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, HashSet};
14use std::{
15 fs::read_to_string,
16 path::{Path, PathBuf},
17 str::FromStr,
18};
19
20use garde::Validate;
21use log::info;
22#[cfg(feature = "nethsm")]
23use nethsm::Connection;
24use serde::{Deserialize, Serialize};
25#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
26use signstar_crypto::{AdministrativeSecretHandling, NonAdministrativeSecretHandling};
27#[cfg(feature = "yubihsm2")]
28use signstar_yubihsm2::Connection as YubiHsm2Connection;
29use strum::{AsRefStr, VariantNames};
30
31use crate::config::SystemConfig;
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};
38
39#[derive(Clone, Debug, Eq, PartialEq)]
41pub enum UserBackendConnection {
42 #[cfg(feature = "nethsm")]
48 NetHsm {
49 admin_secret_handling: AdministrativeSecretHandling,
51
52 non_admin_secret_handling: NonAdministrativeSecretHandling,
54
55 connections: BTreeSet<Connection>,
57
58 mapping: NetHsmUserMapping,
60 },
61
62 #[cfg(feature = "yubihsm2")]
68 YubiHsm2 {
69 admin_secret_handling: AdministrativeSecretHandling,
71
72 non_admin_secret_handling: NonAdministrativeSecretHandling,
74
75 connections: BTreeSet<YubiHsm2Connection>,
77
78 mapping: YubiHsm2UserMapping,
80 },
81}
82
83#[derive(Clone, Copy, Debug)]
85pub enum UserBackendConnectionFilter {
86 All,
88
89 Admin,
91
92 NonAdmin,
94}
95
96#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
108fn validate_confs<T, U>(config_a: &T, config_b: &U) -> garde::Result
109where
110 T: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
111 U: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
112{
113 let duplicate_system_user_ids = {
115 let system_config_user_ids = config_a.system_user_ids();
116 let config_user_ids = config_b.system_user_ids();
117 let duplicates = system_config_user_ids
118 .intersection(&config_user_ids)
119 .map(|system_user_id| system_user_id.to_string())
120 .collect::<HashSet<_>>();
121
122 if duplicates.is_empty() {
123 None
124 } else {
125 let mut duplicates = Vec::from_iter(duplicates);
126 duplicates.sort();
127 Some(format!(
128 "the duplicate system user ID{} {}",
129 if duplicates.len() > 1 { "s" } else { "" },
130 duplicates.join(", ")
131 ))
132 }
133 };
134
135 let duplicate_public_keys = {
137 let system_config_public_keys: HashSet<_> = config_a
138 .authorized_key_entries()
139 .iter()
140 .cloned()
141 .map(|authorized_key| authorized_key.as_ref().public_key())
142 .collect();
143 let config_public_keys: HashSet<_> = config_b
144 .authorized_key_entries()
145 .iter()
146 .cloned()
147 .map(|authorized_key| authorized_key.as_ref().public_key())
148 .collect();
149 let duplicates: HashSet<_> = system_config_public_keys
150 .intersection(&config_public_keys)
151 .cloned()
152 .map(|public_key| {
153 let mut public_key = public_key.clone();
154 public_key.set_comment("");
156 format!("\"{}\"", public_key.to_string())
157 })
158 .collect();
159
160 if duplicates.is_empty() {
161 None
162 } else {
163 let mut duplicates = Vec::from_iter(duplicates);
164 duplicates.sort();
165 Some(format!(
166 "the duplicate SSH public key{} {}",
167 if duplicates.len() > 1 { "s" } else { "" },
168 duplicates.join(", ")
169 ))
170 }
171 };
172
173 let messages = [duplicate_system_user_ids, duplicate_public_keys];
174 let error_messages = {
175 let mut error_messages = Vec::new();
176
177 for message in messages.iter().flatten() {
178 error_messages.push(message.as_str());
179 }
180
181 error_messages
182 };
183
184 match error_messages.len() {
185 0 => Ok(()),
186 1 => Err(garde::Error::new(format!(
187 "contains {}",
188 error_messages.join("\n")
189 ))),
190 _ => Err(garde::Error::new(format!(
191 "contains multiple issues:\n⤷ {}",
192 error_messages.join("\n⤷ ")
193 ))),
194 }
195}
196
197#[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
209fn validate_config_against_optional_config<T, U>(
210 config_a: &Option<T>,
211) -> impl FnOnce(&U, &()) -> garde::Result + '_
212where
213 T: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
214 U: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
215{
216 move |config_b, _| {
217 let Some(config_a) = config_a else {
218 return Ok(());
219 };
220
221 validate_confs(config_a, config_b)
222 }
223}
224
225#[cfg(all(feature = "nethsm", feature = "yubihsm2"))]
237fn validate_two_optional_configs<T, U>(
238 backend_config_a: &Option<T>,
239) -> impl FnOnce(&Option<U>, &()) -> garde::Result + '_
240where
241 T: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
242 U: ConfigAuthorizedKeyEntries + ConfigSystemUserIds,
243{
244 move |backend_config_b, _| {
245 if let Some(backend_config_a) = backend_config_a
246 && let Some(backend_config_b) = backend_config_b
247 {
248 validate_confs(backend_config_a, backend_config_b)?;
249 }
250
251 Ok(())
252 }
253}
254
255#[derive(AsRefStr, Clone, Copy, Debug, Default, strum::Display, VariantNames)]
257#[strum(serialize_all = "lowercase")]
258enum ConfigFileFormat {
259 #[default]
260 Yaml,
261}
262
263#[derive(Clone, Debug, Default, Deserialize, Serialize, Validate)]
267#[serde(rename_all = "snake_case")]
268pub struct Config {
269 #[cfg_attr(
272 feature = "nethsm",
273 garde(custom(validate_config_against_optional_config(&self.nethsm)))
274 )]
275 #[cfg_attr(
277 feature = "yubihsm2",
278 garde(custom(validate_config_against_optional_config(&self.yubihsm2)))
279 )]
280 #[garde(dive)]
281 system: SystemConfig,
282
283 #[cfg(feature = "nethsm")]
289 #[cfg_attr(
291 all(feature = "nethsm", feature = "yubihsm2"),
292 garde(custom(validate_two_optional_configs(&self.yubihsm2)))
293 )]
294 #[garde(dive)]
295 #[serde(skip_serializing_if = "Option::is_none")]
296 nethsm: Option<NetHsmConfig>,
297
298 #[cfg(feature = "yubihsm2")]
304 #[cfg_attr(
306 all(feature = "nethsm", feature = "yubihsm2"),
307 garde(custom(validate_two_optional_configs(&self.nethsm)))
308 )]
309 #[garde(dive)]
310 #[serde(skip_serializing_if = "Option::is_none")]
311 yubihsm2: Option<YubiHsm2Config>,
312}
313
314impl Config {
315 pub const DEFAULT_CONFIG_DIR: &str = "/usr/share/signstar/";
317
318 pub const RUN_OVERRIDE_CONFIG_DIR: &str = "/run/signstar/";
320
321 pub const ETC_OVERRIDE_CONFIG_DIR: &str = "/etc/signstar/";
323
324 pub const CONFIG_NAME: &str = "config";
326
327 pub fn default_system_path() -> PathBuf {
329 PathBuf::from(Self::DEFAULT_CONFIG_DIR).join(PathBuf::from(format!(
330 "{}.{}",
331 Self::CONFIG_NAME,
332 ConfigFileFormat::default()
333 )))
334 }
335
336 pub fn first_existing_system_path() -> Result<PathBuf, crate::Error> {
342 let path = Self::list_config_file_paths()
343 .into_iter()
344 .find(|path| path.is_file());
345 path.ok_or(crate::ConfigError::ConfigIsMissing.into())
346 }
347
348 pub fn list_config_dirs() -> Vec<PathBuf> {
352 [
353 Self::DEFAULT_CONFIG_DIR,
354 Self::RUN_OVERRIDE_CONFIG_DIR,
355 Self::ETC_OVERRIDE_CONFIG_DIR,
356 ]
357 .iter()
358 .map(PathBuf::from)
359 .collect()
360 }
361
362 pub fn list_config_file_paths() -> Vec<PathBuf> {
366 Self::list_config_dirs()
367 .into_iter()
368 .map(|dir| {
369 dir.join(
370 PathBuf::from(Self::CONFIG_NAME)
371 .with_added_extension(ConfigFileFormat::default().as_ref()),
372 )
373 })
374 .collect()
375 }
376
377 fn from_yaml_str(s: &str) -> Result<Self, crate::Error> {
383 let config: Self =
384 serde_saphyr::from_str(s).map_err(|source| crate::ConfigError::YamlDeserialize {
385 context: "creating a Signstar configuration object".to_string(),
386 source,
387 })?;
388
389 config
390 .validate()
391 .map_err(|source| crate::Error::Validation {
392 context: "validating a Signstar configuration object".to_string(),
393 source,
394 })?;
395
396 Ok(config)
397 }
398
399 fn from_yaml_file(path: impl AsRef<Path>) -> Result<Self, crate::Error> {
407 let path = path.as_ref();
408 info!("Reading Signstar configuration file {path:?}");
409
410 let config_data = read_to_string(path).map_err(|source| crate::Error::IoPath {
411 path: path.to_path_buf(),
412 context: "reading it to string",
413 source,
414 })?;
415 Self::from_yaml_str(&config_data)
416 }
417
418 pub fn from_file_path(path: impl AsRef<Path>) -> Result<Self, crate::Error> {
429 let path = path.as_ref();
430 let extension = {
431 let Some(extension) = path.extension() else {
432 return Err(crate::ConfigError::MissingFileExtension {
433 path: path.to_path_buf(),
434 }
435 .into());
436 };
437 extension.to_string_lossy().to_string()
438 };
439
440 if !ConfigFileFormat::VARIANTS.contains(&extension.as_ref()) {
441 return Err(crate::ConfigError::UnsupportedFileExtension {
442 path: path.to_path_buf(),
443 extension,
444 }
445 .into());
446 }
447
448 Self::from_yaml_file(path)
449 }
450
451 pub fn from_system_path() -> Result<Self, crate::Error> {
463 Self::from_yaml_file(Self::first_existing_system_path()?)
464 }
465
466 pub fn to_yaml_string(&self) -> Result<String, crate::Error> {
472 serde_saphyr::to_string(&self).map_err(|source| {
473 crate::ConfigError::YamlSerialize {
474 context: "serializing Signstar config",
475 source,
476 }
477 .into()
478 })
479 }
480
481 pub fn system(&self) -> &SystemConfig {
483 &self.system
484 }
485
486 #[cfg(feature = "nethsm")]
488 pub fn nethsm(&self) -> Option<&NetHsmConfig> {
489 self.nethsm.as_ref()
490 }
491
492 #[cfg(feature = "yubihsm2")]
494 pub fn yubihsm2(&self) -> Option<&YubiHsm2Config> {
495 self.yubihsm2.as_ref()
496 }
497}
498
499impl FromStr for Config {
500 type Err = crate::Error;
501
502 fn from_str(s: &str) -> Result<Self, Self::Err> {
508 Config::from_yaml_str(s)
509 }
510}
511
512#[derive(Clone, Debug)]
514pub struct ConfigBuilder(Config);
515
516impl ConfigBuilder {
517 #[cfg(feature = "nethsm")]
519 pub fn set_nethsm_config(mut self, nethsm: NetHsmConfig) -> Self {
520 self.0.nethsm = Some(nethsm);
521 self
522 }
523
524 #[cfg(feature = "yubihsm2")]
526 pub fn set_yubihsm2_config(mut self, yubihsm2: YubiHsm2Config) -> Self {
527 self.0.yubihsm2 = Some(yubihsm2);
528 self
529 }
530
531 pub fn finish(self) -> Result<Config, crate::Error> {
537 self.0
538 .validate()
539 .map_err(|source| crate::Error::Validation {
540 context: "validating a configuration object".to_string(),
541 source,
542 })?;
543
544 Ok(self.0)
545 }
546}
547
548#[cfg(test)]
549mod tests {
550 use std::{collections::BTreeSet, num::NonZeroUsize, thread::current};
551
552 use insta::{assert_snapshot, with_settings};
553 #[cfg(feature = "nethsm")]
554 use nethsm::ConnectionSecurity;
555 use pretty_assertions::assert_eq;
556 use rstest::{fixture, rstest};
557 use signstar_crypto::{AdministrativeSecretHandling, NonAdministrativeSecretHandling};
558 #[cfg(any(feature = "nethsm", feature = "yubihsm2"))]
559 use signstar_crypto::{
560 key::{CryptographicKeyContext, KeyMechanism, KeyType, SignatureType, SigningKeySetup},
561 openpgp::OpenPgpUserIdList,
562 };
563 #[cfg(feature = "yubihsm2")]
564 use signstar_yubihsm2::object::Domain;
565 use tempfile::{NamedTempFile, TempDir};
566 use testresult::TestResult;
567
568 use super::*;
569 #[cfg(feature = "nethsm")]
570 use crate::NetHsmMetricsUsers;
571 use crate::{AuthorizedKeyEntry, SystemUserId};
572 use crate::{ConfigError, config::SystemUserMapping};
573
574 const SNAPSHOT_PATH: &str = "fixtures/file/";
575
576 #[fixture]
578 fn default_system_config() -> TestResult<SystemConfig> {
579 Ok(SystemConfig::new(
580 1,
581 AdministrativeSecretHandling::ShamirsSecretSharing {
582 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
583 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
584 },
585 NonAdministrativeSecretHandling::SystemdCreds,
586 BTreeSet::from_iter([
587 SystemUserMapping::ShareHolder {
588 system_user: "share-holder1".parse()?,
589 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
590 },
591 SystemUserMapping::ShareHolder {
592 system_user: "share-holder2".parse()?,
593 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
594 },
595 SystemUserMapping::ShareHolder {
596 system_user: "share-holder3".parse()?,
597 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
598 },
599 SystemUserMapping::WireGuardDownload {
600 system_user: "wireguard-downloader".parse()?,
601 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
602 },
603 ]),
604 )?)
605 }
606
607 #[cfg(feature = "nethsm")]
609 #[fixture]
610 fn default_nethsm_config() -> TestResult<NetHsmConfig> {
611 Ok(NetHsmConfig::new(
612 BTreeSet::from_iter([
613 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
614 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
615 ]),
616 BTreeSet::from_iter([
617 NetHsmUserMapping::Admin("admin".parse()?),
618 NetHsmUserMapping::Backup{
619 backend_user: "backup".parse()?,
620 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
621 system_user: "nethsm-backup-user".parse()?,
622 },
623 NetHsmUserMapping::HermeticMetrics {
624 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
625 system_user: "nethsm-hermetic-metrics-user".parse()?,
626 },
627 NetHsmUserMapping::Metrics {
628 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
629 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
630 system_user: "nethsm-metrics-user".parse()?,
631 },
632 NetHsmUserMapping::Signing {
633 backend_user: "signing".parse()?,
634 signing_key_id: "signing1".parse()?,
635 key_setup: SigningKeySetup::new(
636 KeyType::Curve25519,
637 vec![KeyMechanism::EdDsaSignature],
638 None,
639 SignatureType::EdDsa,
640 CryptographicKeyContext::OpenPgp {
641 user_ids: OpenPgpUserIdList::new(vec![
642 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
643 ])?,
644 version: "v4".parse()?,
645 },
646 )?,
647 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
648 system_user: "nethsm-signing-user".parse()?,
649 tag: "signing1".to_string(),
650 }
651 ]),
652 )?)
653 }
654
655 #[cfg(feature = "yubihsm2")]
657 #[fixture]
658 fn default_yubihsm2_config() -> TestResult<YubiHsm2Config> {
659 Ok(YubiHsm2Config::new(
660 BTreeSet::from_iter([
661 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
662 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
663 ]),
664 BTreeSet::from_iter([
665 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
666 YubiHsm2UserMapping::AuditLog {
667 authentication_key_id: "3".parse()?,
668 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
669 system_user: "yubihsm2-metrics-user".parse()?,
670 },
671 YubiHsm2UserMapping::Backup{
672 authentication_key_id: "2".parse()?,
673 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
674 system_user: "yubihsm2-backup-user".parse()?,
675 wrapping_key_id: "1".parse()?,
676 },
677 YubiHsm2UserMapping::HermeticAuditLog {
678 authentication_key_id: "4".parse()?,
679 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
680 },
681 YubiHsm2UserMapping::Signing {
682 authentication_key_id: "5".parse()?,
683 signing_key_id: "1".parse()?,
684 key_setup: SigningKeySetup::new(
685 KeyType::Curve25519,
686 vec![KeyMechanism::EdDsaSignature],
687 None,
688 SignatureType::EdDsa,
689 CryptographicKeyContext::OpenPgp {
690 user_ids: OpenPgpUserIdList::new(vec![
691 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
692 ])?,
693 version: "v4".parse()?,
694 },
695 )?,
696 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
697 system_user: "yubihsm2-signing-user".parse()?,
698 domain: Domain::One,
699 }
700 ]),
701 )?)
702 }
703
704 #[test]
706 fn config_default_system_path() {
707 assert_eq!(
708 Config::default_system_path(),
709 PathBuf::from("/usr/share/signstar/config.yaml")
710 )
711 }
712
713 #[test]
715 fn config_list_config_file_paths() {
716 assert_eq!(
717 Config::list_config_file_paths(),
718 vec![
719 PathBuf::from("/usr/share/signstar/config.yaml"),
720 PathBuf::from("/run/signstar/config.yaml"),
721 PathBuf::from("/etc/signstar/config.yaml"),
722 ]
723 )
724 }
725
726 #[rstest]
728 fn config_from_file_path_fails_on_missing_file_extension() -> TestResult {
729 let temp_dir = TempDir::new()?;
730
731 match Config::from_file_path(temp_dir.path().join("config")) {
732 Ok(config) => panic!(
733 "Should have failed to create a Config object, but succeeded instead: {config:?}"
734 ),
735 Err(crate::Error::Config(ConfigError::MissingFileExtension { .. })) => {}
736 Err(error) => panic!(
737 "Should have failed with a ConfigError::MissingFileExtension, but failed with a different error instead: {error}"
738 ),
739 }
740
741 Ok(())
742 }
743
744 #[rstest]
746 fn config_from_file_path_fails_on_unsupported_file_extension() -> TestResult {
747 let temp_file = NamedTempFile::with_suffix(".toml")?;
748
749 match Config::from_file_path(temp_file.path()) {
750 Ok(config) => panic!(
751 "Should have failed to create a Config object, but succeeded instead: {config:?}"
752 ),
753 Err(crate::Error::Config(ConfigError::UnsupportedFileExtension { .. })) => {}
754 Err(error) => panic!(
755 "Should have failed with a ConfigError::UnsupportedFileExtension, but failed with a different error instead: {error}"
756 ),
757 }
758
759 Ok(())
760 }
761
762 #[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
764 mod no_backend {
765 use std::collections::HashSet;
766
767 use pretty_assertions::assert_eq;
768
769 use super::*;
770 use crate::config::{ConfigAuthorizedKeyEntries, ConfigSystemUserIds};
771
772 #[fixture]
774 fn default_config(default_system_config: TestResult<SystemConfig>) -> TestResult<Config> {
775 Ok(ConfigBuilder::new(default_system_config?).finish()?)
776 }
777
778 #[rstest]
781 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
782 let config = default_config?;
783 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
784 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
785 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
786 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
787 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
788 ]);
789
790 assert_eq!(
791 config.authorized_key_entries(),
792 expected.iter().collect::<HashSet<_>>()
793 );
794 Ok(())
795 }
796
797 #[rstest]
799 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
800 let config = default_config?;
801 let expected: HashSet<SystemUserId> = HashSet::from_iter([
802 "share-holder1".parse()?,
803 "share-holder2".parse()?,
804 "share-holder3".parse()?,
805 "wireguard-downloader".parse()?,
806 ]);
807
808 assert_eq!(
809 config.system_user_ids(),
810 expected.iter().collect::<HashSet<_>>()
811 );
812 Ok(())
813 }
814 }
815
816 #[cfg(all(feature = "nethsm", not(feature = "yubihsm2")))]
818 mod nethsm_backend {
819 use pretty_assertions::assert_eq;
820
821 use super::*;
822
823 #[fixture]
825 fn default_config(
826 default_system_config: TestResult<SystemConfig>,
827 default_nethsm_config: TestResult<NetHsmConfig>,
828 ) -> TestResult<Config> {
829 Ok(ConfigBuilder::new(default_system_config?)
830 .set_nethsm_config(default_nethsm_config?)
831 .finish()?)
832 }
833
834 #[rstest]
840 #[case::two_duplicate_system_users_two_duplicate_ssh_public_keys(
841 "Configuration with system-wide and NetHSM configuration has two duplicate system users and two duplicate SSH public keys",
842 NetHsmConfig::new(
843 BTreeSet::from_iter([
844 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
845 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
846 ]),
847 BTreeSet::from_iter([
848 NetHsmUserMapping::Admin("admin".parse()?),
849 NetHsmUserMapping::Backup{
850 backend_user: "backup".parse()?,
851 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
852 system_user: "share-holder1".parse()?,
853 },
854 NetHsmUserMapping::HermeticMetrics {
855 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
856 system_user: "nethsm-hermetic-metrics-user".parse()?,
857 },
858 NetHsmUserMapping::Metrics {
859 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
860 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
861 system_user: "share-holder2".parse()?,
862 },
863 NetHsmUserMapping::Signing {
864 backend_user: "signing".parse()?,
865 signing_key_id: "signing1".parse()?,
866 key_setup: SigningKeySetup::new(
867 KeyType::Curve25519,
868 vec![KeyMechanism::EdDsaSignature],
869 None,
870 SignatureType::EdDsa,
871 CryptographicKeyContext::OpenPgp {
872 user_ids: OpenPgpUserIdList::new(vec![
873 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
874 ])?,
875 version: "v4".parse()?,
876 },
877 )?,
878 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
879 system_user: "nethsm-signing-user".parse()?,
880 tag: "signing1".to_string(),
881 }
882 ]),
883 )?
884 )]
885 #[case::one_duplicate_system_user_two_duplicate_ssh_public_keys(
886 "Configuration with system-wide and NetHSM configuration has one duplicate system user and two duplicate SSH public keys",
887 NetHsmConfig::new(
888 BTreeSet::from_iter([
889 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
890 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
891 ]),
892 BTreeSet::from_iter([
893 NetHsmUserMapping::Admin("admin".parse()?),
894 NetHsmUserMapping::Backup{
895 backend_user: "backup".parse()?,
896 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
897 system_user: "share-holder1".parse()?,
898 },
899 NetHsmUserMapping::HermeticMetrics {
900 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
901 system_user: "nethsm-hermetic-metrics-user".parse()?,
902 },
903 NetHsmUserMapping::Metrics {
904 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
905 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
906 system_user: "nethsm-metrics-user".parse()?,
907 },
908 NetHsmUserMapping::Signing {
909 backend_user: "signing".parse()?,
910 signing_key_id: "signing1".parse()?,
911 key_setup: SigningKeySetup::new(
912 KeyType::Curve25519,
913 vec![KeyMechanism::EdDsaSignature],
914 None,
915 SignatureType::EdDsa,
916 CryptographicKeyContext::OpenPgp {
917 user_ids: OpenPgpUserIdList::new(vec![
918 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
919 ])?,
920 version: "v4".parse()?,
921 },
922 )?,
923 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
924 system_user: "nethsm-signing-user".parse()?,
925 tag: "signing1".to_string(),
926 }
927 ]),
928 )?
929 )]
930 #[case::one_duplicate_system_user_one_duplicate_ssh_public_key(
931 "Configuration with system-wide and NetHSM configuration has one duplicate system user and one duplicate SSH public key",
932 NetHsmConfig::new(
933 BTreeSet::from_iter([
934 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
935 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
936 ]),
937 BTreeSet::from_iter([
938 NetHsmUserMapping::Admin("admin".parse()?),
939 NetHsmUserMapping::Backup{
940 backend_user: "backup".parse()?,
941 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
942 system_user: "share-holder1".parse()?,
943 },
944 NetHsmUserMapping::HermeticMetrics {
945 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
946 system_user: "nethsm-hermetic-metrics-user".parse()?,
947 },
948 NetHsmUserMapping::Metrics {
949 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
950 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
951 system_user: "nethsm-metrics-user".parse()?,
952 },
953 NetHsmUserMapping::Signing {
954 backend_user: "signing".parse()?,
955 signing_key_id: "signing1".parse()?,
956 key_setup: SigningKeySetup::new(
957 KeyType::Curve25519,
958 vec![KeyMechanism::EdDsaSignature],
959 None,
960 SignatureType::EdDsa,
961 CryptographicKeyContext::OpenPgp {
962 user_ids: OpenPgpUserIdList::new(vec![
963 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
964 ])?,
965 version: "v4".parse()?,
966 },
967 )?,
968 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
969 system_user: "nethsm-signing-user".parse()?,
970 tag: "signing1".to_string(),
971 }
972 ]),
973 )?
974 )]
975 #[case::one_duplicate_ssh_public_key(
976 "Configuration with system-wide and NetHSM configuration has one duplicate SSH public key",
977 NetHsmConfig::new(
978 BTreeSet::from_iter([
979 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
980 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
981 ]),
982 BTreeSet::from_iter([
983 NetHsmUserMapping::Admin("admin".parse()?),
984 NetHsmUserMapping::Backup{
985 backend_user: "backup".parse()?,
986 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
987 system_user: "nethsm-backup-user".parse()?,
988 },
989 NetHsmUserMapping::HermeticMetrics {
990 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
991 system_user: "nethsm-hermetic-metrics-user".parse()?,
992 },
993 NetHsmUserMapping::Metrics {
994 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
995 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
996 system_user: "nethsm-metrics-user".parse()?,
997 },
998 NetHsmUserMapping::Signing {
999 backend_user: "signing".parse()?,
1000 signing_key_id: "signing1".parse()?,
1001 key_setup: SigningKeySetup::new(
1002 KeyType::Curve25519,
1003 vec![KeyMechanism::EdDsaSignature],
1004 None,
1005 SignatureType::EdDsa,
1006 CryptographicKeyContext::OpenPgp {
1007 user_ids: OpenPgpUserIdList::new(vec![
1008 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1009 ])?,
1010 version: "v4".parse()?,
1011 },
1012 )?,
1013 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1014 system_user: "nethsm-signing-user".parse()?,
1015 tag: "signing1".to_string(),
1016 }
1017 ]),
1018 )?
1019 )]
1020 #[case::one_duplicate_system_user(
1021 "Configuration with system-wide and NetHSM configuration has one duplicate system user",
1022 NetHsmConfig::new(
1023 BTreeSet::from_iter([
1024 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1025 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1026 ]),
1027 BTreeSet::from_iter([
1028 NetHsmUserMapping::Admin("admin".parse()?),
1029 NetHsmUserMapping::Backup{
1030 backend_user: "backup".parse()?,
1031 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1032 system_user: "share-holder1".parse()?,
1033 },
1034 NetHsmUserMapping::HermeticMetrics {
1035 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1036 system_user: "nethsm-hermetic-metrics-user".parse()?,
1037 },
1038 NetHsmUserMapping::Metrics {
1039 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1040 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1041 system_user: "nethsm-metrics-user".parse()?,
1042 },
1043 NetHsmUserMapping::Signing {
1044 backend_user: "signing".parse()?,
1045 signing_key_id: "signing1".parse()?,
1046 key_setup: SigningKeySetup::new(
1047 KeyType::Curve25519,
1048 vec![KeyMechanism::EdDsaSignature],
1049 None,
1050 SignatureType::EdDsa,
1051 CryptographicKeyContext::OpenPgp {
1052 user_ids: OpenPgpUserIdList::new(vec![
1053 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1054 ])?,
1055 version: "v4".parse()?,
1056 },
1057 )?,
1058 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1059 system_user: "nethsm-signing-user".parse()?,
1060 tag: "signing1".to_string(),
1061 }
1062 ]),
1063 )?
1064 )]
1065 fn config_builder_fails_validation(
1066 default_system_config: TestResult<SystemConfig>,
1067 #[case] description: &str,
1068 #[case] nethsm_config: NetHsmConfig,
1069 ) -> TestResult {
1070 let error_message = match ConfigBuilder::new(default_system_config?)
1071 .set_nethsm_config(nethsm_config)
1072 .finish()
1073 {
1074 Err(error) => error.to_string(),
1075 Ok(config) => panic!(
1076 "Expected to fail with Error::Validation, but succeeded instead: {}",
1077 config.to_yaml_string()?
1078 ),
1079 };
1080
1081 with_settings!({
1082 description => description,
1083 snapshot_path => SNAPSHOT_PATH,
1084 prepend_module_to_snapshot => false,
1085 }, {
1086 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
1087 });
1088
1089 Ok(())
1090 }
1091
1092 #[rstest]
1094 fn config_nethsm(
1095 default_system_config: TestResult<SystemConfig>,
1096 default_nethsm_config: TestResult<NetHsmConfig>,
1097 ) -> TestResult {
1098 let nethsm_config = default_nethsm_config?;
1099
1100 let config = ConfigBuilder::new(default_system_config?)
1101 .set_nethsm_config(nethsm_config.clone())
1102 .finish()?;
1103
1104 assert_eq!(
1105 &nethsm_config,
1106 config.nethsm().expect("a NetHsmConfig reference")
1107 );
1108
1109 Ok(())
1110 }
1111
1112 #[rstest]
1114 #[case::nethsm_signing(
1115 "nethsm-signing-user",
1116 Some(UserBackendConnection::NetHsm {
1117 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1118 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1119 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1120 },
1121 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1122 connections: BTreeSet::from_iter([
1123 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1124 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1125 ]),
1126 mapping: NetHsmUserMapping::Signing {
1127 backend_user: "signing".parse()?,
1128 signing_key_id: "signing1".parse()?,
1129 key_setup: SigningKeySetup::new(
1130 KeyType::Curve25519,
1131 vec![KeyMechanism::EdDsaSignature],
1132 None,
1133 SignatureType::EdDsa,
1134 CryptographicKeyContext::OpenPgp {
1135 user_ids: OpenPgpUserIdList::new(vec![
1136 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1137 ])?,
1138 version: "v4".parse()?,
1139 },
1140 )?,
1141 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1142 system_user: "nethsm-signing-user".parse()?,
1143 tag: "signing1".to_string(),
1144 }
1145 })
1146 )]
1147 #[case::none("foo", None)]
1148 fn config_user_backend_connection(
1149 default_config: TestResult<Config>,
1150 #[case] system_user: &str,
1151 #[case] expected_connection: Option<UserBackendConnection>,
1152 ) -> TestResult {
1153 let config = default_config?;
1154 assert_eq!(
1155 expected_connection,
1156 config.user_backend_connection(&system_user.parse()?)
1157 );
1158
1159 Ok(())
1160 }
1161
1162 #[rstest]
1165 #[case::filter_all(
1166 UserBackendConnectionFilter::All,
1167 vec![
1168 UserBackendConnection::NetHsm {
1169 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1170 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1171 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1172 },
1173 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1174 connections: BTreeSet::from_iter([
1175 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1176 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1177 ]),
1178 mapping: NetHsmUserMapping::Admin("admin".parse()?)
1179 },
1180 UserBackendConnection::NetHsm {
1181 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1182 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1183 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1184 },
1185 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1186 connections: BTreeSet::from_iter([
1187 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1188 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1189 ]),
1190 mapping: NetHsmUserMapping::Backup{
1191 backend_user: "backup".parse()?,
1192 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1193 system_user: "nethsm-backup-user".parse()?,
1194 }
1195 },
1196 UserBackendConnection::NetHsm {
1197 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1198 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1199 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1200 },
1201 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1202 connections: BTreeSet::from_iter([
1203 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1204 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1205 ]),
1206 mapping: NetHsmUserMapping::HermeticMetrics {
1207 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1208 system_user: "nethsm-hermetic-metrics-user".parse()?,
1209 }
1210 },
1211 UserBackendConnection::NetHsm {
1212 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1213 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1214 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1215 },
1216 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1217 connections: BTreeSet::from_iter([
1218 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1219 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1220 ]),
1221 mapping: NetHsmUserMapping::Metrics {
1222 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1223 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1224 system_user: "nethsm-metrics-user".parse()?,
1225 }
1226 },
1227 UserBackendConnection::NetHsm {
1228 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1229 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1230 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1231 },
1232 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1233 connections: BTreeSet::from_iter([
1234 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1235 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1236 ]),
1237 mapping: NetHsmUserMapping::Signing {
1238 backend_user: "signing".parse()?,
1239 signing_key_id: "signing1".parse()?,
1240 key_setup: SigningKeySetup::new(
1241 KeyType::Curve25519,
1242 vec![KeyMechanism::EdDsaSignature],
1243 None,
1244 SignatureType::EdDsa,
1245 CryptographicKeyContext::OpenPgp {
1246 user_ids: OpenPgpUserIdList::new(vec![
1247 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1248 ])?,
1249 version: "v4".parse()?,
1250 },
1251 )?,
1252 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1253 system_user: "nethsm-signing-user".parse()?,
1254 tag: "signing1".to_string(),
1255 }
1256 },
1257 ],
1258 )]
1259 #[case::filter_admin(
1260 UserBackendConnectionFilter::Admin,
1261 vec![
1262 UserBackendConnection::NetHsm {
1263 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1264 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1265 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1266 },
1267 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1268 connections: BTreeSet::from_iter([
1269 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1270 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1271 ]),
1272 mapping: NetHsmUserMapping::Admin("admin".parse()?)
1273 },
1274 ],
1275 )]
1276 #[case::filter_non_admin(
1277 UserBackendConnectionFilter::NonAdmin,
1278 vec![
1279 UserBackendConnection::NetHsm {
1280 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1281 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1282 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1283 },
1284 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1285 connections: BTreeSet::from_iter([
1286 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1287 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1288 ]),
1289 mapping: NetHsmUserMapping::Backup{
1290 backend_user: "backup".parse()?,
1291 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1292 system_user: "nethsm-backup-user".parse()?,
1293 }
1294 },
1295 UserBackendConnection::NetHsm {
1296 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1297 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1298 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1299 },
1300 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1301 connections: BTreeSet::from_iter([
1302 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1303 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1304 ]),
1305 mapping: NetHsmUserMapping::HermeticMetrics {
1306 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1307 system_user: "nethsm-hermetic-metrics-user".parse()?,
1308 }
1309 },
1310 UserBackendConnection::NetHsm {
1311 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1312 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1313 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1314 },
1315 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1316 connections: BTreeSet::from_iter([
1317 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1318 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1319 ]),
1320 mapping: NetHsmUserMapping::Metrics {
1321 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1322 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1323 system_user: "nethsm-metrics-user".parse()?,
1324 }
1325 },
1326 UserBackendConnection::NetHsm {
1327 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1328 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1329 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1330 },
1331 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1332 connections: BTreeSet::from_iter([
1333 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1334 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1335 ]),
1336 mapping: NetHsmUserMapping::Signing {
1337 backend_user: "signing".parse()?,
1338 signing_key_id: "signing1".parse()?,
1339 key_setup: SigningKeySetup::new(
1340 KeyType::Curve25519,
1341 vec![KeyMechanism::EdDsaSignature],
1342 None,
1343 SignatureType::EdDsa,
1344 CryptographicKeyContext::OpenPgp {
1345 user_ids: OpenPgpUserIdList::new(vec![
1346 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1347 ])?,
1348 version: "v4".parse()?,
1349 },
1350 )?,
1351 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1352 system_user: "nethsm-signing-user".parse()?,
1353 tag: "signing1".to_string(),
1354 }
1355 },
1356 ],
1357 )]
1358 fn config_user_backend_connections(
1359 default_config: TestResult<Config>,
1360 #[case] filter: UserBackendConnectionFilter,
1361 #[case] expected_connections: Vec<UserBackendConnection>,
1362 ) -> TestResult {
1363 let config = default_config?;
1364
1365 assert_eq!(
1366 expected_connections,
1367 config.user_backend_connections(filter)
1368 );
1369
1370 Ok(())
1371 }
1372
1373 #[rstest]
1376 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
1377 let config = default_config?;
1378 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
1379 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
1380 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1381 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1382 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1383 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1384 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1385 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1386 ]);
1387
1388 assert_eq!(
1389 config.authorized_key_entries(),
1390 expected.iter().collect::<HashSet<_>>()
1391 );
1392 Ok(())
1393 }
1394
1395 #[rstest]
1397 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
1398 let config = default_config?;
1399 let expected: HashSet<SystemUserId> = HashSet::from_iter([
1400 "share-holder1".parse()?,
1401 "share-holder2".parse()?,
1402 "share-holder3".parse()?,
1403 "wireguard-downloader".parse()?,
1404 "nethsm-backup-user".parse()?,
1405 "nethsm-hermetic-metrics-user".parse()?,
1406 "nethsm-metrics-user".parse()?,
1407 "nethsm-signing-user".parse()?,
1408 ]);
1409
1410 assert_eq!(
1411 config.system_user_ids(),
1412 expected.iter().collect::<HashSet<_>>()
1413 );
1414 Ok(())
1415 }
1416
1417 #[rstest]
1421 fn config_to_yaml_string(
1422 default_system_config: TestResult<SystemConfig>,
1423 default_nethsm_config: TestResult<NetHsmConfig>,
1424 ) -> TestResult {
1425 let config = ConfigBuilder::new(default_system_config?)
1426 .set_nethsm_config(default_nethsm_config?)
1427 .finish()?;
1428 let config_str = config.to_yaml_string()?;
1429
1430 with_settings!({
1431 description => "Configuration with system-wide and NetHSM configuration",
1432 snapshot_path => SNAPSHOT_PATH,
1433 prepend_module_to_snapshot => false,
1434 }, {
1435 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
1436 });
1437
1438 Ok(())
1439 }
1440
1441 #[rstest]
1446 fn roundtrip_yaml_config(
1447 #[files("../fixtures/config/nethsm_backend/*.yaml")] path: PathBuf,
1448 ) -> TestResult {
1449 let config_string = read_to_string(&path)?;
1450 let config = Config::from_file_path(&path)?;
1451
1452 assert_eq!(config.to_yaml_string()?, config_string);
1453
1454 Ok(())
1455 }
1456
1457 #[rstest]
1461 fn user_backend_connection_secret_handling(
1462 default_config: TestResult<Config>,
1463 ) -> TestResult {
1464 let config = default_config?;
1465 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
1466 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1467 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1468 };
1469 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
1470
1471 let user_backend_connection = config
1472 .user_backend_connection(&"nethsm-signing-user".parse()?)
1473 .expect("there to be a mapping of the requested name");
1474
1475 assert_eq!(
1476 user_backend_connection.admin_secret_handling(),
1477 admin_secret_handling
1478 );
1479 assert_eq!(
1480 user_backend_connection.non_admin_secret_handling(),
1481 non_admin_secret_handling
1482 );
1483
1484 Ok(())
1485 }
1486 }
1487
1488 #[cfg(all(feature = "yubihsm2", not(feature = "nethsm")))]
1490 mod yubihsm2_backend {
1491 use pretty_assertions::assert_eq;
1492
1493 use super::*;
1494
1495 #[fixture]
1497 fn default_config(
1498 default_system_config: TestResult<SystemConfig>,
1499 default_yubihsm2_config: TestResult<YubiHsm2Config>,
1500 ) -> TestResult<Config> {
1501 Ok(ConfigBuilder::new(default_system_config?)
1502 .set_yubihsm2_config(default_yubihsm2_config?)
1503 .finish()?)
1504 }
1505
1506 #[rstest]
1512 #[case::two_duplicate_system_users_two_duplicate_ssh_public_keys(
1513 "Configuration with system-wide and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys",
1514 YubiHsm2Config::new(
1515 BTreeSet::from_iter([
1516 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1517 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1518 ]),
1519 BTreeSet::from_iter([
1520 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1521 YubiHsm2UserMapping::AuditLog {
1522 authentication_key_id: "3".parse()?,
1523 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1524 system_user: "share-holder2".parse()?,
1525 },
1526 YubiHsm2UserMapping::Backup{
1527 authentication_key_id: "2".parse()?,
1528 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1529 system_user: "share-holder1".parse()?,
1530 wrapping_key_id: "1".parse()?,
1531 },
1532 YubiHsm2UserMapping::HermeticAuditLog {
1533 authentication_key_id: "4".parse()?,
1534 system_user: "yubihsm2-hermetic-metrics".parse()?,
1535 },
1536 YubiHsm2UserMapping::Signing {
1537 authentication_key_id: "5".parse()?,
1538 signing_key_id: "1".parse()?,
1539 key_setup: SigningKeySetup::new(
1540 KeyType::Curve25519,
1541 vec![KeyMechanism::EdDsaSignature],
1542 None,
1543 SignatureType::EdDsa,
1544 CryptographicKeyContext::OpenPgp {
1545 user_ids: OpenPgpUserIdList::new(vec![
1546 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1547 ])?,
1548 version: "v4".parse()?,
1549 },
1550 )?,
1551 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1552 system_user: "yubihsm2-signing-user".parse()?,
1553 domain: Domain::One,
1554 }
1555 ]),
1556 )?
1557 )]
1558 #[case::one_duplicate_system_user_two_duplicate_ssh_public_keys(
1559 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user and two duplicate SSH public keys",
1560 YubiHsm2Config::new(
1561 BTreeSet::from_iter([
1562 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1563 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1564 ]),
1565 BTreeSet::from_iter([
1566 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1567 YubiHsm2UserMapping::AuditLog {
1568 authentication_key_id: "3".parse()?,
1569 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1570 system_user: "yubihsm2-metrics-user".parse()?,
1571 },
1572 YubiHsm2UserMapping::Backup{
1573 authentication_key_id: "2".parse()?,
1574 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1575 system_user: "share-holder1".parse()?,
1576 wrapping_key_id: "1".parse()?,
1577 },
1578 YubiHsm2UserMapping::HermeticAuditLog {
1579 authentication_key_id: "4".parse()?,
1580 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
1581 },
1582 YubiHsm2UserMapping::Signing {
1583 authentication_key_id: "5".parse()?,
1584 signing_key_id: "1".parse()?,
1585 key_setup: SigningKeySetup::new(
1586 KeyType::Curve25519,
1587 vec![KeyMechanism::EdDsaSignature],
1588 None,
1589 SignatureType::EdDsa,
1590 CryptographicKeyContext::OpenPgp {
1591 user_ids: OpenPgpUserIdList::new(vec![
1592 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1593 ])?,
1594 version: "v4".parse()?,
1595 },
1596 )?,
1597 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1598 system_user: "yubihsm2-signing-user".parse()?,
1599 domain: Domain::One,
1600 }
1601 ]),
1602 )?
1603 )]
1604 #[case::one_duplicate_system_user_one_duplicate_ssh_public_key(
1605 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user and one duplicate SSH public key",
1606 YubiHsm2Config::new(
1607 BTreeSet::from_iter([
1608 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1609 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1610 ]),
1611 BTreeSet::from_iter([
1612 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1613 YubiHsm2UserMapping::AuditLog {
1614 authentication_key_id: "3".parse()?,
1615 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1616 system_user: "yubihsm2-metrics-user".parse()?,
1617 },
1618 YubiHsm2UserMapping::Backup{
1619 authentication_key_id: "2".parse()?,
1620 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1621 system_user: "share-holder1".parse()?,
1622 wrapping_key_id: "1".parse()?,
1623 },
1624 YubiHsm2UserMapping::HermeticAuditLog {
1625 authentication_key_id: "4".parse()?,
1626 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
1627 },
1628 YubiHsm2UserMapping::Signing {
1629 authentication_key_id: "5".parse()?,
1630 signing_key_id: "1".parse()?,
1631 key_setup: SigningKeySetup::new(
1632 KeyType::Curve25519,
1633 vec![KeyMechanism::EdDsaSignature],
1634 None,
1635 SignatureType::EdDsa,
1636 CryptographicKeyContext::OpenPgp {
1637 user_ids: OpenPgpUserIdList::new(vec![
1638 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1639 ])?,
1640 version: "v4".parse()?,
1641 },
1642 )?,
1643 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1644 system_user: "yubihsm2-signing-user".parse()?,
1645 domain: Domain::One,
1646 }
1647 ]),
1648 )?
1649 )]
1650 #[case::one_duplicate_ssh_public_key(
1651 "Configuration with system-wide and YubiHSM2 configuration has one duplicate SSH public key",
1652 YubiHsm2Config::new(
1653 BTreeSet::from_iter([
1654 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1655 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1656 ]),
1657 BTreeSet::from_iter([
1658 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1659 YubiHsm2UserMapping::AuditLog {
1660 authentication_key_id: "3".parse()?,
1661 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1662 system_user: "yubihsm2-metrics-user".parse()?,
1663 },
1664 YubiHsm2UserMapping::Backup{
1665 authentication_key_id: "2".parse()?,
1666 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1667 system_user: "yubihsm2-backup-user".parse()?,
1668 wrapping_key_id: "1".parse()?,
1669 },
1670 YubiHsm2UserMapping::HermeticAuditLog {
1671 authentication_key_id: "4".parse()?,
1672 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
1673 },
1674 YubiHsm2UserMapping::Signing {
1675 authentication_key_id: "5".parse()?,
1676 signing_key_id: "1".parse()?,
1677 key_setup: SigningKeySetup::new(
1678 KeyType::Curve25519,
1679 vec![KeyMechanism::EdDsaSignature],
1680 None,
1681 SignatureType::EdDsa,
1682 CryptographicKeyContext::OpenPgp {
1683 user_ids: OpenPgpUserIdList::new(vec![
1684 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1685 ])?,
1686 version: "v4".parse()?,
1687 },
1688 )?,
1689 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1690 system_user: "yubihsm2-signing-user".parse()?,
1691 domain: Domain::One,
1692 }
1693 ]),
1694 )?
1695 )]
1696 #[case::one_duplicate_system_user(
1697 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user",
1698 YubiHsm2Config::new(
1699 BTreeSet::from_iter([
1700 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1701 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1702 ]),
1703 BTreeSet::from_iter([
1704 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1705 YubiHsm2UserMapping::AuditLog {
1706 authentication_key_id: "3".parse()?,
1707 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1708 system_user: "yubihsm2-metrics-user".parse()?,
1709 },
1710 YubiHsm2UserMapping::Backup{
1711 authentication_key_id: "2".parse()?,
1712 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1713 system_user: "share-holder1".parse()?,
1714 wrapping_key_id: "1".parse()?,
1715 },
1716 YubiHsm2UserMapping::HermeticAuditLog {
1717 authentication_key_id: "4".parse()?,
1718 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
1719 },
1720 YubiHsm2UserMapping::Signing {
1721 authentication_key_id: "5".parse()?,
1722 signing_key_id: "1".parse()?,
1723 key_setup: SigningKeySetup::new(
1724 KeyType::Curve25519,
1725 vec![KeyMechanism::EdDsaSignature],
1726 None,
1727 SignatureType::EdDsa,
1728 CryptographicKeyContext::OpenPgp {
1729 user_ids: OpenPgpUserIdList::new(vec![
1730 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1731 ])?,
1732 version: "v4".parse()?,
1733 },
1734 )?,
1735 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1736 system_user: "yubihsm2-signing-user".parse()?,
1737 domain: Domain::One,
1738 }
1739 ]),
1740 )?
1741 )]
1742 fn config_builder_fails_validation(
1743 default_system_config: TestResult<SystemConfig>,
1744 #[case] description: &str,
1745 #[case] yubihsm2_config: YubiHsm2Config,
1746 ) -> TestResult {
1747 let error_message = match ConfigBuilder::new(default_system_config?)
1748 .set_yubihsm2_config(yubihsm2_config)
1749 .finish()
1750 {
1751 Err(error) => error.to_string(),
1752 Ok(config) => panic!(
1753 "Expected to fail with Error::Validation, but succeeded instead: {}",
1754 config.to_yaml_string()?
1755 ),
1756 };
1757
1758 with_settings!({
1759 description => description,
1760 snapshot_path => SNAPSHOT_PATH,
1761 prepend_module_to_snapshot => false,
1762 }, {
1763 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
1764 });
1765
1766 Ok(())
1767 }
1768
1769 #[rstest]
1771 fn config_yubihsm2(
1772 default_system_config: TestResult<SystemConfig>,
1773 default_yubihsm2_config: TestResult<YubiHsm2Config>,
1774 ) -> TestResult {
1775 let yubihsm2_config = default_yubihsm2_config?;
1776
1777 let config = ConfigBuilder::new(default_system_config?)
1778 .set_yubihsm2_config(yubihsm2_config.clone())
1779 .finish()?;
1780
1781 assert_eq!(
1782 &yubihsm2_config,
1783 config.yubihsm2().expect("a YubiHsm2Config reference")
1784 );
1785
1786 Ok(())
1787 }
1788
1789 #[rstest]
1791 #[case::yubihsm2_signing(
1792 "yubihsm2-signing-user",
1793 Some(UserBackendConnection::YubiHsm2 {
1794 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1795 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1796 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1797 },
1798 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1799 connections: BTreeSet::from_iter([
1800 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1801 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1802 ]),
1803 mapping: YubiHsm2UserMapping::Signing {
1804 authentication_key_id: "5".parse()?,
1805 signing_key_id: "1".parse()?,
1806 key_setup: SigningKeySetup::new(
1807 KeyType::Curve25519,
1808 vec![KeyMechanism::EdDsaSignature],
1809 None,
1810 SignatureType::EdDsa,
1811 CryptographicKeyContext::OpenPgp {
1812 user_ids: OpenPgpUserIdList::new(vec![
1813 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1814 ])?,
1815 version: "v4".parse()?,
1816 },
1817 )?,
1818 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1819 system_user: "yubihsm2-signing-user".parse()?,
1820 domain: Domain::One,
1821 }
1822 })
1823 )]
1824 #[case::none("foo", None)]
1825 fn config_user_backend_connection(
1826 default_config: TestResult<Config>,
1827 #[case] system_user: &str,
1828 #[case] expected_connection: Option<UserBackendConnection>,
1829 ) -> TestResult {
1830 let config = default_config?;
1831 assert_eq!(
1832 expected_connection,
1833 config.user_backend_connection(&system_user.parse()?)
1834 );
1835
1836 Ok(())
1837 }
1838
1839 #[rstest]
1842 #[case::filter_all(
1843 UserBackendConnectionFilter::All,
1844 vec![
1845 UserBackendConnection::YubiHsm2 {
1846 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1847 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1848 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1849 },
1850 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1851 connections: BTreeSet::from_iter([
1852 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1853 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1854 ]),
1855 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1856 },
1857 UserBackendConnection::YubiHsm2 {
1858 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1859 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1860 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1861 },
1862 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1863 connections: BTreeSet::from_iter([
1864 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1865 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1866 ]),
1867 mapping: YubiHsm2UserMapping::AuditLog {
1868 authentication_key_id: "3".parse()?,
1869 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1870 system_user: "yubihsm2-metrics-user".parse()?,
1871 },
1872 },
1873 UserBackendConnection::YubiHsm2 {
1874 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1875 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1876 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1877 },
1878 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1879 connections: BTreeSet::from_iter([
1880 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1881 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1882 ]),
1883 mapping: YubiHsm2UserMapping::Backup{
1884 authentication_key_id: "2".parse()?,
1885 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1886 system_user: "yubihsm2-backup-user".parse()?,
1887 wrapping_key_id: "1".parse()?,
1888 },
1889 },
1890 UserBackendConnection::YubiHsm2 {
1891 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1892 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1893 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1894 },
1895 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1896 connections: BTreeSet::from_iter([
1897 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1898 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1899 ]),
1900 mapping: YubiHsm2UserMapping::HermeticAuditLog {
1901 authentication_key_id: "4".parse()?,
1902 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
1903 },
1904 },
1905 UserBackendConnection::YubiHsm2 {
1906 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1907 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1908 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1909 },
1910 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1911 connections: BTreeSet::from_iter([
1912 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1913 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1914 ]),
1915 mapping: YubiHsm2UserMapping::Signing {
1916 authentication_key_id: "5".parse()?,
1917 signing_key_id: "1".parse()?,
1918 key_setup: SigningKeySetup::new(
1919 KeyType::Curve25519,
1920 vec![KeyMechanism::EdDsaSignature],
1921 None,
1922 SignatureType::EdDsa,
1923 CryptographicKeyContext::OpenPgp {
1924 user_ids: OpenPgpUserIdList::new(vec![
1925 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1926 ])?,
1927 version: "v4".parse()?,
1928 },
1929 )?,
1930 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1931 system_user: "yubihsm2-signing-user".parse()?,
1932 domain: Domain::One,
1933 }
1934 },
1935 ],
1936 )]
1937 #[case::filter_admin(
1938 UserBackendConnectionFilter::Admin,
1939 vec![
1940 UserBackendConnection::YubiHsm2 {
1941 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1942 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1943 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1944 },
1945 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1946 connections: BTreeSet::from_iter([
1947 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1948 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1949 ]),
1950 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1951 },
1952 ],
1953 )]
1954 #[case::filter_non_admin(
1955 UserBackendConnectionFilter::NonAdmin,
1956 vec![
1957 UserBackendConnection::YubiHsm2 {
1958 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1959 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1960 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1961 },
1962 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1963 connections: BTreeSet::from_iter([
1964 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1965 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1966 ]),
1967 mapping: YubiHsm2UserMapping::AuditLog {
1968 authentication_key_id: "3".parse()?,
1969 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1970 system_user: "yubihsm2-metrics-user".parse()?,
1971 },
1972 },
1973 UserBackendConnection::YubiHsm2 {
1974 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1975 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1976 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1977 },
1978 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1979 connections: BTreeSet::from_iter([
1980 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1981 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1982 ]),
1983 mapping: YubiHsm2UserMapping::Backup{
1984 authentication_key_id: "2".parse()?,
1985 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
1986 system_user: "yubihsm2-backup-user".parse()?,
1987 wrapping_key_id: "1".parse()?,
1988 },
1989 },
1990 UserBackendConnection::YubiHsm2 {
1991 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1992 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1993 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1994 },
1995 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1996 connections: BTreeSet::from_iter([
1997 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1998 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1999 ]),
2000 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2001 authentication_key_id: "4".parse()?,
2002 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2003 },
2004 },
2005 UserBackendConnection::YubiHsm2 {
2006 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2007 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2008 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2009 },
2010 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2011 connections: BTreeSet::from_iter([
2012 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2013 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2014 ]),
2015 mapping: YubiHsm2UserMapping::Signing {
2016 authentication_key_id: "5".parse()?,
2017 signing_key_id: "1".parse()?,
2018 key_setup: SigningKeySetup::new(
2019 KeyType::Curve25519,
2020 vec![KeyMechanism::EdDsaSignature],
2021 None,
2022 SignatureType::EdDsa,
2023 CryptographicKeyContext::OpenPgp {
2024 user_ids: OpenPgpUserIdList::new(vec![
2025 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2026 ])?,
2027 version: "v4".parse()?,
2028 },
2029 )?,
2030 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2031 system_user: "yubihsm2-signing-user".parse()?,
2032 domain: Domain::One,
2033 }
2034 },
2035 ],
2036 )]
2037 fn config_user_backend_connections(
2038 default_config: TestResult<Config>,
2039 #[case] filter: UserBackendConnectionFilter,
2040 #[case] expected_connections: Vec<UserBackendConnection>,
2041 ) -> TestResult {
2042 let config = default_config?;
2043
2044 assert_eq!(
2045 expected_connections,
2046 config.user_backend_connections(filter)
2047 );
2048
2049 Ok(())
2050 }
2051
2052 #[rstest]
2056 fn config_to_yaml_string(
2057 default_system_config: TestResult<SystemConfig>,
2058 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2059 ) -> TestResult {
2060 let config = ConfigBuilder::new(default_system_config?)
2061 .set_yubihsm2_config(default_yubihsm2_config?)
2062 .finish()?;
2063 let config_str = config.to_yaml_string()?;
2064
2065 with_settings!({
2066 description => "Configuration with system-wide and YubiHSM2 configuration",
2067 snapshot_path => SNAPSHOT_PATH,
2068 prepend_module_to_snapshot => false,
2069 }, {
2070 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
2071 });
2072
2073 Ok(())
2074 }
2075
2076 #[rstest]
2079 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
2080 let config = default_config?;
2081 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
2082 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
2083 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2084 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2085 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2086 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2087 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2088 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2089 ]);
2090
2091 assert_eq!(
2092 config.authorized_key_entries(),
2093 expected.iter().collect::<HashSet<_>>()
2094 );
2095 Ok(())
2096 }
2097
2098 #[rstest]
2100 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
2101 let config = default_config?;
2102 let expected: HashSet<SystemUserId> = HashSet::from_iter([
2103 "share-holder1".parse()?,
2104 "share-holder2".parse()?,
2105 "share-holder3".parse()?,
2106 "wireguard-downloader".parse()?,
2107 "yubihsm2-metrics-user".parse()?,
2108 "yubihsm2-backup-user".parse()?,
2109 "yubihsm2-hermetic-metrics-user".parse()?,
2110 "yubihsm2-signing-user".parse()?,
2111 ]);
2112
2113 assert_eq!(
2114 config.system_user_ids(),
2115 expected.iter().collect::<HashSet<_>>()
2116 );
2117 Ok(())
2118 }
2119
2120 #[rstest]
2125 #[cfg(not(feature = "_yubihsm2-mockhsm"))]
2126 fn roundtrip_yaml_config(
2127 #[files("../fixtures/config/yubihsm2_backend/*.yaml")] path: PathBuf,
2128 ) -> TestResult {
2129 let config_string = read_to_string(&path)?;
2130 let config = Config::from_file_path(&path)?;
2131
2132 assert_eq!(config.to_yaml_string()?, config_string);
2133
2134 Ok(())
2135 }
2136
2137 #[rstest]
2142 #[cfg(feature = "_yubihsm2-mockhsm")]
2143 fn roundtrip_yaml_config_mockhsm(
2144 #[files("../fixtures/config/yubihsm2_mockhsm_backend/*.yaml")] path: PathBuf,
2145 ) -> TestResult {
2146 let config_string = read_to_string(&path)?;
2147 let config = Config::from_file_path(&path)?;
2148
2149 assert_eq!(config.to_yaml_string()?, config_string);
2150
2151 Ok(())
2152 }
2153
2154 #[rstest]
2158 fn user_backend_connection_secret_handling(
2159 default_config: TestResult<Config>,
2160 ) -> TestResult {
2161 let config = default_config?;
2162 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
2163 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2164 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2165 };
2166 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
2167
2168 let user_backend_connection = config
2169 .user_backend_connection(&"yubihsm2-signing-user".parse()?)
2170 .expect("there to be a mapping of the requested name");
2171
2172 assert_eq!(
2173 user_backend_connection.admin_secret_handling(),
2174 admin_secret_handling
2175 );
2176 assert_eq!(
2177 user_backend_connection.non_admin_secret_handling(),
2178 non_admin_secret_handling
2179 );
2180
2181 Ok(())
2182 }
2183 }
2184
2185 #[cfg(all(feature = "nethsm", feature = "yubihsm2"))]
2187 mod all_backends {
2188 use pretty_assertions::assert_eq;
2189
2190 use super::*;
2191
2192 #[fixture]
2194 fn default_config(
2195 default_system_config: TestResult<SystemConfig>,
2196 default_nethsm_config: TestResult<NetHsmConfig>,
2197 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2198 ) -> TestResult<Config> {
2199 Ok(ConfigBuilder::new(default_system_config?)
2200 .set_nethsm_config(default_nethsm_config?)
2201 .set_yubihsm2_config(default_yubihsm2_config?)
2202 .finish()?)
2203 }
2204
2205 #[rstest]
2212 #[case::backend_overlap_duplicate_system_users_two_duplicate_ssh_public_keys(
2213 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys in the backends",
2214 NetHsmConfig::new(
2215 BTreeSet::from_iter([
2216 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2217 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2218 ]),
2219 BTreeSet::from_iter([
2220 NetHsmUserMapping::Admin("admin".parse()?),
2221 NetHsmUserMapping::Backup{
2222 backend_user: "backup".parse()?,
2223 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2224 system_user: "backup-user".parse()?,
2225 },
2226 NetHsmUserMapping::HermeticMetrics {
2227 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2228 system_user: "nethsm-hermetic-metrics-user".parse()?,
2229 },
2230 NetHsmUserMapping::Metrics {
2231 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2232 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2233 system_user: "metrics-user".parse()?,
2234 },
2235 NetHsmUserMapping::Signing {
2236 backend_user: "signing".parse()?,
2237 signing_key_id: "signing1".parse()?,
2238 key_setup: SigningKeySetup::new(
2239 KeyType::Curve25519,
2240 vec![KeyMechanism::EdDsaSignature],
2241 None,
2242 SignatureType::EdDsa,
2243 CryptographicKeyContext::OpenPgp {
2244 user_ids: OpenPgpUserIdList::new(vec![
2245 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2246 ])?,
2247 version: "v4".parse()?,
2248 },
2249 )?,
2250 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2251 system_user: "nethsm-signing-user".parse()?,
2252 tag: "nethsm-signing1".to_string(),
2253 }
2254 ]),
2255 )?,
2256 YubiHsm2Config::new(
2257 BTreeSet::from_iter([
2258 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2259 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2260 ]),
2261 BTreeSet::from_iter([
2262 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2263 YubiHsm2UserMapping::AuditLog {
2264 authentication_key_id: "3".parse()?,
2265 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2266 system_user: "metrics-user".parse()?,
2267 },
2268 YubiHsm2UserMapping::Backup {
2269 authentication_key_id: "2".parse()?,
2270 wrapping_key_id: "2".parse()?,
2271 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2272 system_user: "backup-user".parse()?,
2273 },
2274 YubiHsm2UserMapping::HermeticAuditLog {
2275 authentication_key_id: "4".parse()?,
2276 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2277 },
2278 YubiHsm2UserMapping::Signing {
2279 authentication_key_id: "5".parse()?,
2280 key_setup: SigningKeySetup::new(
2281 KeyType::Curve25519,
2282 vec![KeyMechanism::EdDsaSignature],
2283 None,
2284 SignatureType::EdDsa,
2285 CryptographicKeyContext::OpenPgp {
2286 user_ids: OpenPgpUserIdList::new(vec![
2287 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2288 ])?,
2289 version: "v4".parse()?,
2290 },
2291 )?,
2292 signing_key_id: "1".parse()?,
2293 domain: Domain::One,
2294 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2295 system_user: "yubihsm2-signing-user".parse()? }
2296 ]),
2297 )?,
2298 )]
2299 #[case::backend_overlap_one_duplicate_system_user(
2300 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has one duplicate system user in the backends",
2301 NetHsmConfig::new(
2302 BTreeSet::from_iter([
2303 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2304 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2305 ]),
2306 BTreeSet::from_iter([
2307 NetHsmUserMapping::Admin("admin".parse()?),
2308 NetHsmUserMapping::Backup{
2309 backend_user: "backup".parse()?,
2310 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2311 system_user: "backup-user".parse()?,
2312 },
2313 NetHsmUserMapping::HermeticMetrics {
2314 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2315 system_user: "nethsm-hermetic-metrics-user".parse()?,
2316 },
2317 NetHsmUserMapping::Metrics {
2318 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2319 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2320 system_user: "nethsm-metrics-user".parse()?,
2321 },
2322 NetHsmUserMapping::Signing {
2323 backend_user: "signing".parse()?,
2324 signing_key_id: "signing1".parse()?,
2325 key_setup: SigningKeySetup::new(
2326 KeyType::Curve25519,
2327 vec![KeyMechanism::EdDsaSignature],
2328 None,
2329 SignatureType::EdDsa,
2330 CryptographicKeyContext::OpenPgp {
2331 user_ids: OpenPgpUserIdList::new(vec![
2332 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2333 ])?,
2334 version: "v4".parse()?,
2335 },
2336 )?,
2337 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2338 system_user: "nethsm-signing-user".parse()?,
2339 tag: "nethsm-signing1".to_string(),
2340 }
2341 ]),
2342 )?,
2343 YubiHsm2Config::new(
2344 BTreeSet::from_iter([
2345 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2346 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2347 ]),
2348 BTreeSet::from_iter([
2349 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2350 YubiHsm2UserMapping::AuditLog {
2351 authentication_key_id: "3".parse()?,
2352 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2353 system_user: "yubihsm2-metrics-user".parse()?,
2354 },
2355 YubiHsm2UserMapping::Backup {
2356 authentication_key_id: "2".parse()?,
2357 wrapping_key_id: "2".parse()?,
2358 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2359 system_user: "backup-user".parse()?,
2360 },
2361 YubiHsm2UserMapping::HermeticAuditLog {
2362 authentication_key_id: "4".parse()?,
2363 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2364 },
2365 YubiHsm2UserMapping::Signing {
2366 authentication_key_id: "5".parse()?,
2367 key_setup: SigningKeySetup::new(
2368 KeyType::Curve25519,
2369 vec![KeyMechanism::EdDsaSignature],
2370 None,
2371 SignatureType::EdDsa,
2372 CryptographicKeyContext::OpenPgp {
2373 user_ids: OpenPgpUserIdList::new(vec![
2374 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2375 ])?,
2376 version: "v4".parse()?,
2377 },
2378 )?,
2379 signing_key_id: "1".parse()?,
2380 domain: Domain::One,
2381 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2382 system_user: "yubihsm2-signing-user".parse()? }
2383 ]),
2384 )?,
2385 )]
2386 #[case::system_overlap_duplicate_system_users_two_duplicate_ssh_public_keys(
2387 "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",
2388 NetHsmConfig::new(
2389 BTreeSet::from_iter([
2390 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2391 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2392 ]),
2393 BTreeSet::from_iter([
2394 NetHsmUserMapping::Admin("admin".parse()?),
2395 NetHsmUserMapping::Backup{
2396 backend_user: "backup".parse()?,
2397 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
2398 system_user: "share-holder1".parse()?,
2399 },
2400 NetHsmUserMapping::Metrics {
2401 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2402 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2403 system_user: "share-holder2".parse()?,
2404 },
2405 NetHsmUserMapping::HermeticMetrics {
2406 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2407 system_user: "nethsm-hermetic-metrics-user".parse()?,
2408 },
2409 NetHsmUserMapping::Signing {
2410 backend_user: "signing".parse()?,
2411 signing_key_id: "signing1".parse()?,
2412 key_setup: SigningKeySetup::new(
2413 KeyType::Curve25519,
2414 vec![KeyMechanism::EdDsaSignature],
2415 None,
2416 SignatureType::EdDsa,
2417 CryptographicKeyContext::OpenPgp {
2418 user_ids: OpenPgpUserIdList::new(vec![
2419 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2420 ])?,
2421 version: "v4".parse()?,
2422 },
2423 )?,
2424 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2425 system_user: "nethsm-signing-user".parse()?,
2426 tag: "nethsm-signing1".to_string(),
2427 }
2428 ]),
2429 )?,
2430 YubiHsm2Config::new(
2431 BTreeSet::from_iter([
2432 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2433 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2434 ]),
2435 BTreeSet::from_iter([
2436 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2437 YubiHsm2UserMapping::Backup {
2438 authentication_key_id: "2".parse()?,
2439 wrapping_key_id: "2".parse()?,
2440 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
2441 system_user: "share-holder1".parse()?,
2442 },
2443 YubiHsm2UserMapping::AuditLog {
2444 authentication_key_id: "3".parse()?,
2445 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2446 system_user: "share-holder2".parse()?,
2447 },
2448 YubiHsm2UserMapping::HermeticAuditLog {
2449 authentication_key_id: "4".parse()?,
2450 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2451 },
2452 YubiHsm2UserMapping::Signing {
2453 authentication_key_id: "5".parse()?,
2454 key_setup: SigningKeySetup::new(
2455 KeyType::Curve25519,
2456 vec![KeyMechanism::EdDsaSignature],
2457 None,
2458 SignatureType::EdDsa,
2459 CryptographicKeyContext::OpenPgp {
2460 user_ids: OpenPgpUserIdList::new(vec![
2461 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2462 ])?,
2463 version: "v4".parse()?,
2464 },
2465 )?,
2466 signing_key_id: "1".parse()?,
2467 domain: Domain::One,
2468 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2469 system_user: "yubihsm2-signing-user".parse()? }
2470 ]),
2471 )?,
2472 )]
2473 fn config_fails_validation(
2474 default_system_config: TestResult<SystemConfig>,
2475 #[case] description: &str,
2476 #[case] nethsm_config: NetHsmConfig,
2477 #[case] yubihsm2_config: YubiHsm2Config,
2478 ) -> TestResult {
2479 let error_message = match ConfigBuilder::new(default_system_config?)
2480 .set_nethsm_config(nethsm_config)
2481 .set_yubihsm2_config(yubihsm2_config)
2482 .finish()
2483 {
2484 Err(error) => error.to_string(),
2485 Ok(config) => panic!(
2486 "Expected to fail with Error::Validation, but succeeded instead: {}",
2487 config.to_yaml_string()?
2488 ),
2489 };
2490
2491 with_settings!({
2492 description => description,
2493 snapshot_path => SNAPSHOT_PATH,
2494 prepend_module_to_snapshot => false,
2495 }, {
2496 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
2497 });
2498
2499 Ok(())
2500 }
2501
2502 #[rstest]
2504 #[case::nethsm_signing(
2505 "nethsm-signing-user",
2506 Some(UserBackendConnection::NetHsm {
2507 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2508 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2509 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2510 },
2511 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2512 connections: BTreeSet::from_iter([
2513 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2514 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2515 ]),
2516 mapping: NetHsmUserMapping::Signing {
2517 backend_user: "signing".parse()?,
2518 signing_key_id: "signing1".parse()?,
2519 key_setup: SigningKeySetup::new(
2520 KeyType::Curve25519,
2521 vec![KeyMechanism::EdDsaSignature],
2522 None,
2523 SignatureType::EdDsa,
2524 CryptographicKeyContext::OpenPgp {
2525 user_ids: OpenPgpUserIdList::new(vec![
2526 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2527 ])?,
2528 version: "v4".parse()?,
2529 },
2530 )?,
2531 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2532 system_user: "nethsm-signing-user".parse()?,
2533 tag: "signing1".to_string(),
2534 }
2535 })
2536 )]
2537 #[case::yubihsm2_signing(
2538 "yubihsm2-signing-user",
2539 Some(UserBackendConnection::YubiHsm2 {
2540 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2541 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2542 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2543 },
2544 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2545 connections: BTreeSet::from_iter([
2546 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2547 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2548 ]),
2549 mapping: YubiHsm2UserMapping::Signing {
2550 authentication_key_id: "5".parse()?,
2551 signing_key_id: "1".parse()?,
2552 key_setup: SigningKeySetup::new(
2553 KeyType::Curve25519,
2554 vec![KeyMechanism::EdDsaSignature],
2555 None,
2556 SignatureType::EdDsa,
2557 CryptographicKeyContext::OpenPgp {
2558 user_ids: OpenPgpUserIdList::new(vec![
2559 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2560 ])?,
2561 version: "v4".parse()?,
2562 },
2563 )?,
2564 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2565 system_user: "yubihsm2-signing-user".parse()?,
2566 domain: Domain::One,
2567 }
2568 })
2569 )]
2570 #[case::none("foo", None)]
2571 fn config_user_backend_connection(
2572 default_config: TestResult<Config>,
2573 #[case] system_user: &str,
2574 #[case] expected_connection: Option<UserBackendConnection>,
2575 ) -> TestResult {
2576 let config = default_config?;
2577 assert_eq!(
2578 expected_connection,
2579 config.user_backend_connection(&system_user.parse()?)
2580 );
2581
2582 Ok(())
2583 }
2584
2585 #[rstest]
2588 #[case::filter_all(
2589 UserBackendConnectionFilter::All,
2590 vec![
2591 UserBackendConnection::NetHsm {
2592 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2593 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2594 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2595 },
2596 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2597 connections: BTreeSet::from_iter([
2598 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2599 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2600 ]),
2601 mapping: NetHsmUserMapping::Admin("admin".parse()?)
2602 },
2603 UserBackendConnection::NetHsm {
2604 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2605 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2606 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2607 },
2608 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2609 connections: BTreeSet::from_iter([
2610 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2611 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2612 ]),
2613 mapping: NetHsmUserMapping::Backup{
2614 backend_user: "backup".parse()?,
2615 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2616 system_user: "nethsm-backup-user".parse()?,
2617 }
2618 },
2619 UserBackendConnection::NetHsm {
2620 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2621 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2622 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2623 },
2624 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2625 connections: BTreeSet::from_iter([
2626 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2627 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2628 ]),
2629 mapping: NetHsmUserMapping::HermeticMetrics {
2630 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2631 system_user: "nethsm-hermetic-metrics-user".parse()?,
2632 }
2633 },
2634 UserBackendConnection::NetHsm {
2635 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2636 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2637 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2638 },
2639 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2640 connections: BTreeSet::from_iter([
2641 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2642 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2643 ]),
2644 mapping: NetHsmUserMapping::Metrics {
2645 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2646 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2647 system_user: "nethsm-metrics-user".parse()?,
2648 }
2649 },
2650 UserBackendConnection::NetHsm {
2651 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2652 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2653 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2654 },
2655 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2656 connections: BTreeSet::from_iter([
2657 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2658 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2659 ]),
2660 mapping: NetHsmUserMapping::Signing {
2661 backend_user: "signing".parse()?,
2662 signing_key_id: "signing1".parse()?,
2663 key_setup: SigningKeySetup::new(
2664 KeyType::Curve25519,
2665 vec![KeyMechanism::EdDsaSignature],
2666 None,
2667 SignatureType::EdDsa,
2668 CryptographicKeyContext::OpenPgp {
2669 user_ids: OpenPgpUserIdList::new(vec![
2670 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2671 ])?,
2672 version: "v4".parse()?,
2673 },
2674 )?,
2675 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2676 system_user: "nethsm-signing-user".parse()?,
2677 tag: "signing1".to_string(),
2678 }
2679 },
2680 UserBackendConnection::YubiHsm2 {
2681 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2682 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2683 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2684 },
2685 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2686 connections: BTreeSet::from_iter([
2687 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2688 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2689 ]),
2690 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2691 },
2692 UserBackendConnection::YubiHsm2 {
2693 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2694 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2695 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2696 },
2697 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2698 connections: BTreeSet::from_iter([
2699 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2700 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2701 ]),
2702 mapping: YubiHsm2UserMapping::AuditLog {
2703 authentication_key_id: "3".parse()?,
2704 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2705 system_user: "yubihsm2-metrics-user".parse()?,
2706 },
2707 },
2708 UserBackendConnection::YubiHsm2 {
2709 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2710 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2711 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2712 },
2713 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2714 connections: BTreeSet::from_iter([
2715 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2716 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2717 ]),
2718 mapping: YubiHsm2UserMapping::Backup{
2719 authentication_key_id: "2".parse()?,
2720 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2721 system_user: "yubihsm2-backup-user".parse()?,
2722 wrapping_key_id: "1".parse()?,
2723 },
2724 },
2725 UserBackendConnection::YubiHsm2 {
2726 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2727 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2728 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2729 },
2730 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2731 connections: BTreeSet::from_iter([
2732 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2733 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2734 ]),
2735 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2736 authentication_key_id: "4".parse()?,
2737 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2738 },
2739 },
2740 UserBackendConnection::YubiHsm2 {
2741 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2742 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2743 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2744 },
2745 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2746 connections: BTreeSet::from_iter([
2747 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2748 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2749 ]),
2750 mapping: YubiHsm2UserMapping::Signing {
2751 authentication_key_id: "5".parse()?,
2752 signing_key_id: "1".parse()?,
2753 key_setup: SigningKeySetup::new(
2754 KeyType::Curve25519,
2755 vec![KeyMechanism::EdDsaSignature],
2756 None,
2757 SignatureType::EdDsa,
2758 CryptographicKeyContext::OpenPgp {
2759 user_ids: OpenPgpUserIdList::new(vec![
2760 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2761 ])?,
2762 version: "v4".parse()?,
2763 },
2764 )?,
2765 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2766 system_user: "yubihsm2-signing-user".parse()?,
2767 domain: Domain::One,
2768 }
2769 },
2770 ],
2771 )]
2772 #[case::filter_admin(
2773 UserBackendConnectionFilter::Admin,
2774 vec![
2775 UserBackendConnection::NetHsm {
2776 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2777 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2778 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2779 },
2780 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2781 connections: BTreeSet::from_iter([
2782 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2783 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2784 ]),
2785 mapping: NetHsmUserMapping::Admin("admin".parse()?)
2786 },
2787 UserBackendConnection::YubiHsm2 {
2788 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2789 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2790 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2791 },
2792 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2793 connections: BTreeSet::from_iter([
2794 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2795 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2796 ]),
2797 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2798 },
2799 ],
2800 )]
2801 #[case::filter_non_admin(
2802 UserBackendConnectionFilter::NonAdmin,
2803 vec![
2804 UserBackendConnection::NetHsm {
2805 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2806 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2807 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2808 },
2809 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2810 connections: BTreeSet::from_iter([
2811 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2812 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2813 ]),
2814 mapping: NetHsmUserMapping::Backup{
2815 backend_user: "backup".parse()?,
2816 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2817 system_user: "nethsm-backup-user".parse()?,
2818 }
2819 },
2820 UserBackendConnection::NetHsm {
2821 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2822 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2823 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2824 },
2825 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2826 connections: BTreeSet::from_iter([
2827 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2828 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2829 ]),
2830 mapping: NetHsmUserMapping::HermeticMetrics {
2831 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2832 system_user: "nethsm-hermetic-metrics-user".parse()?,
2833 }
2834 },
2835 UserBackendConnection::NetHsm {
2836 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2837 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2838 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2839 },
2840 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2841 connections: BTreeSet::from_iter([
2842 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2843 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2844 ]),
2845 mapping: NetHsmUserMapping::Metrics {
2846 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2847 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2848 system_user: "nethsm-metrics-user".parse()?,
2849 }
2850 },
2851 UserBackendConnection::NetHsm {
2852 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2853 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2854 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2855 },
2856 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2857 connections: BTreeSet::from_iter([
2858 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2859 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2860 ]),
2861 mapping: NetHsmUserMapping::Signing {
2862 backend_user: "signing".parse()?,
2863 signing_key_id: "signing1".parse()?,
2864 key_setup: SigningKeySetup::new(
2865 KeyType::Curve25519,
2866 vec![KeyMechanism::EdDsaSignature],
2867 None,
2868 SignatureType::EdDsa,
2869 CryptographicKeyContext::OpenPgp {
2870 user_ids: OpenPgpUserIdList::new(vec![
2871 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2872 ])?,
2873 version: "v4".parse()?,
2874 },
2875 )?,
2876 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2877 system_user: "nethsm-signing-user".parse()?,
2878 tag: "signing1".to_string(),
2879 }
2880 },
2881 UserBackendConnection::YubiHsm2 {
2882 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2883 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2884 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2885 },
2886 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2887 connections: BTreeSet::from_iter([
2888 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2889 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2890 ]),
2891 mapping: YubiHsm2UserMapping::AuditLog {
2892 authentication_key_id: "3".parse()?,
2893 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2894 system_user: "yubihsm2-metrics-user".parse()?,
2895 },
2896 },
2897 UserBackendConnection::YubiHsm2 {
2898 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2899 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2900 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2901 },
2902 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2903 connections: BTreeSet::from_iter([
2904 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2905 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2906 ]),
2907 mapping: YubiHsm2UserMapping::Backup{
2908 authentication_key_id: "2".parse()?,
2909 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2910 system_user: "yubihsm2-backup-user".parse()?,
2911 wrapping_key_id: "1".parse()?,
2912 },
2913 },
2914 UserBackendConnection::YubiHsm2 {
2915 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2916 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2917 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2918 },
2919 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2920 connections: BTreeSet::from_iter([
2921 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2922 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2923 ]),
2924 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2925 authentication_key_id: "4".parse()?,
2926 system_user: "yubihsm2-hermetic-metrics-user".parse()?,
2927 },
2928 },
2929 UserBackendConnection::YubiHsm2 {
2930 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2931 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2932 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2933 },
2934 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2935 connections: BTreeSet::from_iter([
2936 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2937 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2938 ]),
2939 mapping: YubiHsm2UserMapping::Signing {
2940 authentication_key_id: "5".parse()?,
2941 signing_key_id: "1".parse()?,
2942 key_setup: SigningKeySetup::new(
2943 KeyType::Curve25519,
2944 vec![KeyMechanism::EdDsaSignature],
2945 None,
2946 SignatureType::EdDsa,
2947 CryptographicKeyContext::OpenPgp {
2948 user_ids: OpenPgpUserIdList::new(vec![
2949 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2950 ])?,
2951 version: "v4".parse()?,
2952 },
2953 )?,
2954 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2955 system_user: "yubihsm2-signing-user".parse()?,
2956 domain: Domain::One,
2957 }
2958 },
2959 ],
2960 )]
2961 fn config_user_backend_connections(
2962 default_config: TestResult<Config>,
2963 #[case] filter: UserBackendConnectionFilter,
2964 #[case] expected_connections: Vec<UserBackendConnection>,
2965 ) -> TestResult {
2966 let config = default_config?;
2967
2968 assert_eq!(
2969 expected_connections,
2970 config.user_backend_connections(filter)
2971 );
2972
2973 Ok(())
2974 }
2975
2976 #[rstest]
2981 fn config_to_yaml_string(
2982 default_system_config: TestResult<SystemConfig>,
2983 default_nethsm_config: TestResult<NetHsmConfig>,
2984 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2985 ) -> TestResult {
2986 let config = ConfigBuilder::new(default_system_config?)
2987 .set_nethsm_config(default_nethsm_config?)
2988 .set_yubihsm2_config(default_yubihsm2_config?)
2989 .finish()?;
2990 let config_str = config.to_yaml_string()?;
2991
2992 with_settings!({
2993 description => "Configuration with system-wide, NetHSM and YubiHSM2 configuration",
2994 snapshot_path => SNAPSHOT_PATH,
2995 prepend_module_to_snapshot => false,
2996 }, {
2997 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
2998 });
2999
3000 Ok(())
3001 }
3002
3003 #[rstest]
3006 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
3007 let config = default_config?;
3008 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
3009 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3010 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3011 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
3012 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
3013 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3014 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3015 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3016 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3017 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3018 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3019 ]);
3020
3021 assert_eq!(
3022 config.authorized_key_entries(),
3023 expected.iter().collect::<HashSet<_>>()
3024 );
3025 Ok(())
3026 }
3027
3028 #[rstest]
3030 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
3031 let config = default_config?;
3032 let expected: HashSet<SystemUserId> = HashSet::from_iter([
3033 "share-holder1".parse()?,
3034 "share-holder2".parse()?,
3035 "share-holder3".parse()?,
3036 "wireguard-downloader".parse()?,
3037 "nethsm-backup-user".parse()?,
3038 "nethsm-hermetic-metrics-user".parse()?,
3039 "nethsm-metrics-user".parse()?,
3040 "nethsm-signing-user".parse()?,
3041 "yubihsm2-metrics-user".parse()?,
3042 "yubihsm2-backup-user".parse()?,
3043 "yubihsm2-hermetic-metrics-user".parse()?,
3044 "yubihsm2-signing-user".parse()?,
3045 ]);
3046
3047 assert_eq!(
3048 config.system_user_ids(),
3049 expected.iter().collect::<HashSet<_>>()
3050 );
3051 Ok(())
3052 }
3053
3054 #[rstest]
3056 fn config_builder_new(
3057 default_system_config: TestResult<SystemConfig>,
3058 default_nethsm_config: TestResult<NetHsmConfig>,
3059 default_yubihsm2_config: TestResult<YubiHsm2Config>,
3060 ) -> TestResult {
3061 let _config = ConfigBuilder::new(default_system_config?)
3062 .set_nethsm_config(default_nethsm_config?)
3063 .set_yubihsm2_config(default_yubihsm2_config?)
3064 .finish()?;
3065
3066 Ok(())
3067 }
3068
3069 #[rstest]
3075 fn roundtrip_yaml_config(
3076 #[files("../fixtures/config/all_backends/*.yaml")] path: PathBuf,
3077 ) -> TestResult {
3078 let config_string = read_to_string(&path)?;
3079 let config = Config::from_file_path(&path)?;
3080
3081 assert_eq!(config.to_yaml_string()?, config_string);
3082
3083 Ok(())
3084 }
3085
3086 #[rstest]
3090 fn user_backend_connection_secret_handling(
3091 default_config: TestResult<Config>,
3092 ) -> TestResult {
3093 let config = default_config?;
3094 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
3095 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3096 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3097 };
3098 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
3099
3100 for user in ["nethsm-signing-user", "yubihsm2-signing-user"] {
3101 let user_backend_connection = config
3102 .user_backend_connection(&user.parse()?)
3103 .expect("there to be a mapping of the requested name");
3104
3105 assert_eq!(
3106 user_backend_connection.admin_secret_handling(),
3107 admin_secret_handling
3108 );
3109 assert_eq!(
3110 user_backend_connection.non_admin_secret_handling(),
3111 non_admin_secret_handling
3112 );
3113 }
3114
3115 Ok(())
3116 }
3117 }
3118
3119 #[cfg(not(all(feature = "nethsm", feature = "yubihsm2")))]
3121 mod no_backends {
3122 use pretty_assertions::assert_eq;
3123
3124 use super::*;
3125
3126 #[rstest]
3128 fn config_builder_new(default_system_config: TestResult<SystemConfig>) -> TestResult {
3129 let _config = ConfigBuilder::new(default_system_config?).finish()?;
3130
3131 Ok(())
3132 }
3133
3134 #[rstest]
3136 fn config_system(default_system_config: TestResult<SystemConfig>) -> TestResult {
3137 let system_config = default_system_config?;
3138 let config = ConfigBuilder::new(system_config.clone()).finish()?;
3139 assert_eq!(config.system(), &system_config);
3140
3141 Ok(())
3142 }
3143
3144 #[rstest]
3148 fn config_to_yaml_string(default_system_config: TestResult<SystemConfig>) -> TestResult {
3149 let config = ConfigBuilder::new(default_system_config?).finish()?;
3150 let config_str = config.to_yaml_string()?;
3151
3152 with_settings!({
3153 description => "Configuration with system-wide, NetHSM and YubiHSM2 configuration",
3154 snapshot_path => SNAPSHOT_PATH,
3155 prepend_module_to_snapshot => false,
3156 }, {
3157 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
3158 });
3159
3160 Ok(())
3161 }
3162
3163 #[rstest]
3168 fn roundtrip_yaml_config(
3169 #[files("../fixtures/config/no_backend/*.yaml")] path: PathBuf,
3170 ) -> TestResult {
3171 let config_string = read_to_string(&path)?;
3172 let config = Config::from_file_path(&path)?;
3173
3174 assert_eq!(config.to_yaml_string()?, config_string);
3175
3176 Ok(())
3177 }
3178 }
3179}