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