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: "signstar-share-holder1".parse()?,
629 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
630 },
631 SystemUserMapping::ShareHolder {
632 system_user: "signstar-share-holder2".parse()?,
633 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
634 },
635 SystemUserMapping::ShareHolder {
636 system_user: "signstar-share-holder3".parse()?,
637 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?
638 },
639 SystemUserMapping::WireguardDownload {
640 system_user: "signstar-wireguard-download".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 "signstar-share-holder1".parse()?,
654 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?),
655 ),
656 (
657 "signstar-share-holder2".parse()?,
658 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?),
659 ),
660 (
661 "signstar-share-holder3".parse()?,
662 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?),
663 ),
664 (
665 "signstar-wireguard-download".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".parse()?,
686 },
687 NetHsmUserMapping::HermeticMetrics {
688 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
689 system_user: "nethsm-hermetic-metrics".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".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".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".parse()?,
732 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?),
733 ),
734 (
735 "nethsm-hermetic-metrics".parse()?,
736 None,
737 ),
738 (
739 "nethsm-metrics".parse()?,
740 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?),
741 ),
742 (
743 "nethsm-signing".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-audit-log".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".parse()?,
769 },
770 YubiHsm2UserMapping::HermeticAuditLog {
771 authentication_key_id: "4".parse()?,
772 system_user: "yubihsm2-hermetic-audit-log".parse()?,
773 },
774 YubiHsm2UserMapping::Signing {
775 authentication_key_id: "5".parse()?,
776 signing_key_id: "1".parse()?,
777 key_setup: SigningKeySetup::new(
778 KeyType::Curve25519,
779 vec![KeyMechanism::EdDsaSignature],
780 None,
781 SignatureType::EdDsa,
782 CryptographicKeyContext::OpenPgp {
783 user_ids: OpenPgpUserIdList::new(vec![
784 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
785 ])?,
786 version: "v4".parse()?,
787 notations: Default::default(),
788 },
789 )?,
790 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
791 system_user: "yubihsm2-signing".parse()?,
792 domain: Domain::One,
793 }
794 ]),
795 )?)
796 }
797
798 #[cfg(feature = "yubihsm2")]
801 #[fixture]
802 fn raw_user_data_yubihsm2() -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
803 Ok(vec![
804 (
805 SystemUserId::root(),
806 None,
807 ),
808 (
809 "yubihsm2-audit-log".parse()?,
810 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?),
811 ),
812 (
813 "yubihsm2-backup".parse()?,
814 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?),
815 ),
816 (
817 "yubihsm2-hermetic-audit-log".parse()?,
818 None,
819 ),
820 (
821 "yubihsm2-signing".parse()?,
822 Some("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?),
823 ),
824 ])
825 }
826
827 #[test]
829 fn config_default_system_path() {
830 assert_eq!(
831 Config::default_system_path(),
832 PathBuf::from("/usr/share/signstar/config.yaml")
833 )
834 }
835
836 #[test]
838 fn config_list_config_file_paths() {
839 assert_eq!(
840 Config::list_config_file_paths(),
841 vec![
842 PathBuf::from("/usr/share/signstar/config.yaml"),
843 PathBuf::from("/run/signstar/config.yaml"),
844 PathBuf::from("/etc/signstar/config.yaml"),
845 ]
846 )
847 }
848
849 #[rstest]
851 fn config_from_file_path_fails_on_missing_file_extension() -> TestResult {
852 let temp_dir = TempDir::new()?;
853
854 match Config::from_file_path(temp_dir.path().join("config")) {
855 Ok(config) => panic!(
856 "Should have failed to create a Config object, but succeeded instead: {config:?}"
857 ),
858 Err(crate::Error::Config(Error::MissingFileExtension { .. })) => {}
859 Err(error) => panic!(
860 "Should have failed with a ConfigError::MissingFileExtension, but failed with a different error instead: {error}"
861 ),
862 }
863
864 Ok(())
865 }
866
867 #[rstest]
869 fn config_from_file_path_fails_on_unsupported_file_extension() -> TestResult {
870 let temp_file = NamedTempFile::with_suffix(".toml")?;
871
872 match Config::from_file_path(temp_file.path()) {
873 Ok(config) => panic!(
874 "Should have failed to create a Config object, but succeeded instead: {config:?}"
875 ),
876 Err(crate::Error::Config(Error::UnsupportedFileExtension { .. })) => {}
877 Err(error) => panic!(
878 "Should have failed with a ConfigError::UnsupportedFileExtension, but failed with a different error instead: {error}"
879 ),
880 }
881
882 Ok(())
883 }
884
885 #[cfg(not(any(feature = "nethsm", feature = "yubihsm2")))]
887 mod no_backend {
888 use std::collections::HashSet;
889
890 use pretty_assertions::assert_eq;
891
892 use super::*;
893 use crate::config::{
894 ConfigAuthorizedKeyEntries,
895 ConfigSystemUserIds,
896 SystemUserData,
897 traits::ConfigSystemUserData,
898 };
899
900 #[fixture]
902 fn default_config(default_system_config: TestResult<SystemConfig>) -> TestResult<Config> {
903 Ok(ConfigBuilder::new(default_system_config?).finish()?)
904 }
905
906 #[rstest]
908 fn config_builder_new(default_system_config: TestResult<SystemConfig>) -> TestResult {
909 let _config = ConfigBuilder::new(default_system_config?).finish()?;
910
911 Ok(())
912 }
913
914 #[rstest]
916 fn config_system(default_system_config: TestResult<SystemConfig>) -> TestResult {
917 let system_config = default_system_config?;
918 let config = ConfigBuilder::new(system_config.clone()).finish()?;
919 assert_eq!(config.system(), &system_config);
920
921 Ok(())
922 }
923
924 #[rstest]
928 fn config_to_yaml_string(default_system_config: TestResult<SystemConfig>) -> TestResult {
929 let config = ConfigBuilder::new(default_system_config?).finish()?;
930 let config_str = config.to_yaml_string()?;
931
932 with_settings!({
933 description => "Configuration with only system-wide configuration",
934 snapshot_path => SNAPSHOT_PATH,
935 prepend_module_to_snapshot => false,
936 }, {
937 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
938 });
939
940 Ok(())
941 }
942
943 #[rstest]
948 fn roundtrip_yaml_config(
949 #[files("../fixtures/config/no_backend/*.yaml")] path: PathBuf,
950 ) -> TestResult {
951 let config_string = read_to_string(&path)?;
952 let config = Config::from_file_path(&path)?;
953
954 assert_eq!(config.to_yaml_string()?, config_string);
955
956 Ok(())
957 }
958
959 #[rstest]
962 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
963 let config = default_config?;
964 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
965 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
966 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
967 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
968 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
969 ]);
970
971 assert_eq!(
972 config.authorized_key_entries(),
973 expected.iter().collect::<HashSet<_>>()
974 );
975 Ok(())
976 }
977
978 #[rstest]
980 fn config_system_user_data(
981 default_config: TestResult<Config>,
982 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
983 ) -> TestResult {
984 let config = default_config?;
985 let raw_user_data = raw_user_data_system?;
986 let expected: HashSet<SystemUserData> = HashSet::from_iter([
987 SystemUserData::HostShareholder {
988 system_user: &raw_user_data[0].0,
989 ssh_authorized_key: raw_user_data[0]
990 .1
991 .as_ref()
992 .expect("to have SSH authorized key"),
993 },
994 SystemUserData::HostShareholder {
995 system_user: &raw_user_data[1].0,
996 ssh_authorized_key: raw_user_data[1]
997 .1
998 .as_ref()
999 .expect("to have SSH authorized key"),
1000 },
1001 SystemUserData::HostShareholder {
1002 system_user: &raw_user_data[2].0,
1003 ssh_authorized_key: raw_user_data[2]
1004 .1
1005 .as_ref()
1006 .expect("to have SSH authorized key"),
1007 },
1008 SystemUserData::HostDownloadNetworkConfig {
1009 system_user: &raw_user_data[3].0,
1010 ssh_authorized_key: raw_user_data[3]
1011 .1
1012 .as_ref()
1013 .expect("to have SSH authorized key"),
1014 },
1015 ]);
1016
1017 assert_eq!(config.system_user_data(), expected);
1018 Ok(())
1019 }
1020
1021 #[rstest]
1023 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
1024 let config = default_config?;
1025 let expected: HashSet<SystemUserId> = HashSet::from_iter([
1026 "signstar-share-holder1".parse()?,
1027 "signstar-share-holder2".parse()?,
1028 "signstar-share-holder3".parse()?,
1029 "signstar-wireguard-download".parse()?,
1030 ]);
1031
1032 assert_eq!(
1033 config.system_user_ids(),
1034 expected.iter().collect::<HashSet<_>>()
1035 );
1036 Ok(())
1037 }
1038
1039 #[rstest]
1041 fn system_user_config_state_from_config(default_config: TestResult<Config>) -> TestResult {
1042 let config = default_config?;
1043 let state = SystemUserConfigState::from(&config);
1044
1045 assert_eq!(state.system_user_data, config.system_user_data(),);
1046 Ok(())
1047 }
1048 }
1049
1050 #[cfg(all(feature = "nethsm", not(feature = "yubihsm2")))]
1052 mod nethsm_backend {
1053 use pretty_assertions::assert_eq;
1054
1055 use super::*;
1056 use crate::config::{
1057 SystemUserData,
1058 traits::{ConfigSystemUserData, MappingAuthorizedKeyEntry, MappingSystemUserId},
1059 };
1060
1061 #[fixture]
1063 fn default_config(
1064 default_system_config: TestResult<SystemConfig>,
1065 default_nethsm_config: TestResult<NetHsmConfig>,
1066 ) -> TestResult<Config> {
1067 Ok(ConfigBuilder::new(default_system_config?)
1068 .set_nethsm_config(default_nethsm_config?)
1069 .finish()?)
1070 }
1071
1072 #[fixture]
1075 fn raw_user_data(
1076 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1077 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1078 ) -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
1079 let mut data = raw_user_data_system?;
1080 data.extend(raw_user_data_nethsm?);
1081 Ok(data)
1082 }
1083
1084 #[rstest]
1086 fn user_backend_connection_system_user_id(
1087 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1088 ) -> TestResult {
1089 let raw_user_data_nethsm = raw_user_data_nethsm?;
1090 let data = UserBackendConnection::NetHsm {
1091 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1092 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1093 connections: BTreeSet::from_iter([Connection::new(
1094 "https://nethsm1.example.org/".parse()?,
1095 ConnectionSecurity::Unsafe,
1096 )]),
1097 mapping: NetHsmUserMapping::Backup {
1098 backend_user: "backup".parse()?,
1099 ssh_authorized_key: raw_user_data_nethsm[1]
1100 .1
1101 .clone()
1102 .expect("to have an SSH authorized key"),
1103 system_user: raw_user_data_nethsm[1].0.clone(),
1104 },
1105 };
1106 assert_eq!(data.system_user_id(), Some(&raw_user_data_nethsm[1].0));
1107
1108 Ok(())
1109 }
1110
1111 #[rstest]
1114 fn user_backend_connection_authorized_key_entry(
1115 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1116 ) -> TestResult {
1117 let raw_user_data_nethsm = raw_user_data_nethsm?;
1118 let data = UserBackendConnection::NetHsm {
1119 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1120 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1121 connections: BTreeSet::from_iter([Connection::new(
1122 "https://nethsm1.example.org/".parse()?,
1123 ConnectionSecurity::Unsafe,
1124 )]),
1125 mapping: NetHsmUserMapping::Backup {
1126 backend_user: "backup".parse()?,
1127 ssh_authorized_key: raw_user_data_nethsm[1]
1128 .1
1129 .clone()
1130 .expect("to have an SSH authorized key"),
1131 system_user: raw_user_data_nethsm[1].0.clone(),
1132 },
1133 };
1134 assert_eq!(
1135 data.authorized_key_entry(),
1136 Some(
1137 raw_user_data_nethsm[1]
1138 .1
1139 .as_ref()
1140 .expect("to have an SSH authorized key")
1141 )
1142 );
1143
1144 Ok(())
1145 }
1146
1147 #[rstest]
1153 #[case::two_duplicate_system_users_two_duplicate_ssh_public_keys(
1154 "Configuration with system-wide and NetHSM configuration has two duplicate system users and two duplicate SSH public keys",
1155 NetHsmConfig::new(
1156 BTreeSet::from_iter([
1157 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1158 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1159 ]),
1160 BTreeSet::from_iter([
1161 NetHsmUserMapping::Admin("admin".parse()?),
1162 NetHsmUserMapping::Backup{
1163 backend_user: "backup".parse()?,
1164 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1165 system_user: "signstar-share-holder1".parse()?,
1166 },
1167 NetHsmUserMapping::HermeticMetrics {
1168 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1169 system_user: "nethsm-hermetic-metrics".parse()?,
1170 },
1171 NetHsmUserMapping::Metrics {
1172 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1173 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1174 system_user: "signstar-share-holder2".parse()?,
1175 },
1176 NetHsmUserMapping::Signing {
1177 backend_user: "signing".parse()?,
1178 signing_key_id: "signing1".parse()?,
1179 key_setup: SigningKeySetup::new(
1180 KeyType::Curve25519,
1181 vec![KeyMechanism::EdDsaSignature],
1182 None,
1183 SignatureType::EdDsa,
1184 CryptographicKeyContext::OpenPgp {
1185 user_ids: OpenPgpUserIdList::new(vec![
1186 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1187 ])?,
1188 notations: Default::default(),
1189 version: "v4".parse()?,
1190 },
1191 )?,
1192 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1193 system_user: "nethsm-signing".parse()?,
1194 tag: "signing1".to_string(),
1195 }
1196 ]),
1197 )?
1198 )]
1199 #[case::one_duplicate_system_user_two_duplicate_ssh_public_keys(
1200 "Configuration with system-wide and NetHSM configuration has one duplicate system user and two duplicate SSH public keys",
1201 NetHsmConfig::new(
1202 BTreeSet::from_iter([
1203 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1204 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1205 ]),
1206 BTreeSet::from_iter([
1207 NetHsmUserMapping::Admin("admin".parse()?),
1208 NetHsmUserMapping::Backup{
1209 backend_user: "backup".parse()?,
1210 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1211 system_user: "signstar-share-holder1".parse()?,
1212 },
1213 NetHsmUserMapping::HermeticMetrics {
1214 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1215 system_user: "nethsm-hermetic-metrics".parse()?,
1216 },
1217 NetHsmUserMapping::Metrics {
1218 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1219 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1220 system_user: "nethsm-metrics".parse()?,
1221 },
1222 NetHsmUserMapping::Signing {
1223 backend_user: "signing".parse()?,
1224 signing_key_id: "signing1".parse()?,
1225 key_setup: SigningKeySetup::new(
1226 KeyType::Curve25519,
1227 vec![KeyMechanism::EdDsaSignature],
1228 None,
1229 SignatureType::EdDsa,
1230 CryptographicKeyContext::OpenPgp {
1231 user_ids: OpenPgpUserIdList::new(vec![
1232 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1233 ])?,
1234 notations: Default::default(),
1235 version: "v4".parse()?,
1236 },
1237 )?,
1238 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1239 system_user: "nethsm-signing".parse()?,
1240 tag: "signing1".to_string(),
1241 }
1242 ]),
1243 )?
1244 )]
1245 #[case::one_duplicate_system_user_one_duplicate_ssh_public_key(
1246 "Configuration with system-wide and NetHSM configuration has one duplicate system user and one duplicate SSH public key",
1247 NetHsmConfig::new(
1248 BTreeSet::from_iter([
1249 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1250 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1251 ]),
1252 BTreeSet::from_iter([
1253 NetHsmUserMapping::Admin("admin".parse()?),
1254 NetHsmUserMapping::Backup{
1255 backend_user: "backup".parse()?,
1256 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1257 system_user: "signstar-share-holder1".parse()?,
1258 },
1259 NetHsmUserMapping::HermeticMetrics {
1260 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1261 system_user: "nethsm-hermetic-metrics".parse()?,
1262 },
1263 NetHsmUserMapping::Metrics {
1264 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1265 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1266 system_user: "nethsm-metrics".parse()?,
1267 },
1268 NetHsmUserMapping::Signing {
1269 backend_user: "signing".parse()?,
1270 signing_key_id: "signing1".parse()?,
1271 key_setup: SigningKeySetup::new(
1272 KeyType::Curve25519,
1273 vec![KeyMechanism::EdDsaSignature],
1274 None,
1275 SignatureType::EdDsa,
1276 CryptographicKeyContext::OpenPgp {
1277 user_ids: OpenPgpUserIdList::new(vec![
1278 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1279 ])?,
1280 notations: Default::default(),
1281 version: "v4".parse()?,
1282 },
1283 )?,
1284 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1285 system_user: "nethsm-signing".parse()?,
1286 tag: "signing1".to_string(),
1287 }
1288 ]),
1289 )?
1290 )]
1291 #[case::one_duplicate_ssh_public_key(
1292 "Configuration with system-wide and NetHSM configuration has one duplicate SSH public key",
1293 NetHsmConfig::new(
1294 BTreeSet::from_iter([
1295 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1296 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1297 ]),
1298 BTreeSet::from_iter([
1299 NetHsmUserMapping::Admin("admin".parse()?),
1300 NetHsmUserMapping::Backup{
1301 backend_user: "backup".parse()?,
1302 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1303 system_user: "nethsm-backup".parse()?,
1304 },
1305 NetHsmUserMapping::HermeticMetrics {
1306 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1307 system_user: "nethsm-hermetic-metrics".parse()?,
1308 },
1309 NetHsmUserMapping::Metrics {
1310 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1311 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1312 system_user: "nethsm-metrics".parse()?,
1313 },
1314 NetHsmUserMapping::Signing {
1315 backend_user: "signing".parse()?,
1316 signing_key_id: "signing1".parse()?,
1317 key_setup: SigningKeySetup::new(
1318 KeyType::Curve25519,
1319 vec![KeyMechanism::EdDsaSignature],
1320 None,
1321 SignatureType::EdDsa,
1322 CryptographicKeyContext::OpenPgp {
1323 user_ids: OpenPgpUserIdList::new(vec![
1324 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1325 ])?,
1326 notations: Default::default(),
1327 version: "v4".parse()?,
1328 },
1329 )?,
1330 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1331 system_user: "nethsm-signing".parse()?,
1332 tag: "signing1".to_string(),
1333 }
1334 ]),
1335 )?
1336 )]
1337 #[case::one_duplicate_system_user(
1338 "Configuration with system-wide and NetHSM configuration has one duplicate system user",
1339 NetHsmConfig::new(
1340 BTreeSet::from_iter([
1341 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1342 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1343 ]),
1344 BTreeSet::from_iter([
1345 NetHsmUserMapping::Admin("admin".parse()?),
1346 NetHsmUserMapping::Backup{
1347 backend_user: "backup".parse()?,
1348 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1349 system_user: "signstar-share-holder1".parse()?,
1350 },
1351 NetHsmUserMapping::HermeticMetrics {
1352 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1353 system_user: "nethsm-hermetic-metrics".parse()?,
1354 },
1355 NetHsmUserMapping::Metrics {
1356 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1357 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1358 system_user: "nethsm-metrics".parse()?,
1359 },
1360 NetHsmUserMapping::Signing {
1361 backend_user: "signing".parse()?,
1362 signing_key_id: "signing1".parse()?,
1363 key_setup: SigningKeySetup::new(
1364 KeyType::Curve25519,
1365 vec![KeyMechanism::EdDsaSignature],
1366 None,
1367 SignatureType::EdDsa,
1368 CryptographicKeyContext::OpenPgp {
1369 user_ids: OpenPgpUserIdList::new(vec![
1370 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1371 ])?,
1372 notations: Default::default(),
1373 version: "v4".parse()?,
1374 },
1375 )?,
1376 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1377 system_user: "nethsm-signing".parse()?,
1378 tag: "signing1".to_string(),
1379 }
1380 ]),
1381 )?
1382 )]
1383 fn config_builder_fails_validation(
1384 default_system_config: TestResult<SystemConfig>,
1385 #[case] description: &str,
1386 #[case] nethsm_config: NetHsmConfig,
1387 ) -> TestResult {
1388 let error_message = match ConfigBuilder::new(default_system_config?)
1389 .set_nethsm_config(nethsm_config)
1390 .finish()
1391 {
1392 Err(error) => error.to_string(),
1393 Ok(config) => panic!(
1394 "Expected to fail with Error::Validation, but succeeded instead: {}",
1395 config.to_yaml_string()?
1396 ),
1397 };
1398
1399 with_settings!({
1400 description => description,
1401 snapshot_path => SNAPSHOT_PATH,
1402 prepend_module_to_snapshot => false,
1403 }, {
1404 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
1405 });
1406
1407 Ok(())
1408 }
1409
1410 #[rstest]
1412 fn config_nethsm(
1413 default_system_config: TestResult<SystemConfig>,
1414 default_nethsm_config: TestResult<NetHsmConfig>,
1415 ) -> TestResult {
1416 let nethsm_config = default_nethsm_config?;
1417
1418 let config = ConfigBuilder::new(default_system_config?)
1419 .set_nethsm_config(nethsm_config.clone())
1420 .finish()?;
1421
1422 assert_eq!(
1423 &nethsm_config,
1424 config.nethsm().expect("a NetHsmConfig reference")
1425 );
1426
1427 Ok(())
1428 }
1429
1430 #[rstest]
1432 #[case::nethsm_signing(
1433 "nethsm-signing",
1434 Some(UserBackendConnection::NetHsm {
1435 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1436 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1437 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1438 },
1439 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1440 connections: BTreeSet::from_iter([
1441 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1442 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1443 ]),
1444 mapping: NetHsmUserMapping::Signing {
1445 backend_user: "signing".parse()?,
1446 signing_key_id: "signing1".parse()?,
1447 key_setup: SigningKeySetup::new(
1448 KeyType::Curve25519,
1449 vec![KeyMechanism::EdDsaSignature],
1450 None,
1451 SignatureType::EdDsa,
1452 CryptographicKeyContext::OpenPgp {
1453 user_ids: OpenPgpUserIdList::new(vec![
1454 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1455 ])?,
1456 notations: Default::default(),
1457 version: "v4".parse()?,
1458 },
1459 )?,
1460 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1461 system_user: "nethsm-signing".parse()?,
1462 tag: "signing1".to_string(),
1463 }
1464 })
1465 )]
1466 #[case::none("foo", None)]
1467 fn config_user_backend_connection(
1468 default_config: TestResult<Config>,
1469 #[case] system_user: &str,
1470 #[case] expected_connection: Option<UserBackendConnection>,
1471 ) -> TestResult {
1472 let config = default_config?;
1473 assert_eq!(
1474 expected_connection,
1475 config.user_backend_connection(&system_user.parse()?)
1476 );
1477
1478 Ok(())
1479 }
1480
1481 #[rstest]
1484 #[case::no_filter(
1485 &[],
1486 vec![
1487 UserBackendConnection::NetHsm {
1488 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1489 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1490 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1491 },
1492 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1493 connections: BTreeSet::from_iter([
1494 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1495 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1496 ]),
1497 mapping: NetHsmUserMapping::Admin("admin".parse()?)
1498 },
1499 UserBackendConnection::NetHsm {
1500 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1501 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1502 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1503 },
1504 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1505 connections: BTreeSet::from_iter([
1506 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1507 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1508 ]),
1509 mapping: NetHsmUserMapping::Backup{
1510 backend_user: "backup".parse()?,
1511 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1512 system_user: "nethsm-backup".parse()?,
1513 }
1514 },
1515 UserBackendConnection::NetHsm {
1516 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1517 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1518 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1519 },
1520 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1521 connections: BTreeSet::from_iter([
1522 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1523 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1524 ]),
1525 mapping: NetHsmUserMapping::HermeticMetrics {
1526 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1527 system_user: "nethsm-hermetic-metrics".parse()?,
1528 }
1529 },
1530 UserBackendConnection::NetHsm {
1531 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1532 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1533 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1534 },
1535 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1536 connections: BTreeSet::from_iter([
1537 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1538 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1539 ]),
1540 mapping: NetHsmUserMapping::Metrics {
1541 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1542 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1543 system_user: "nethsm-metrics".parse()?,
1544 }
1545 },
1546 UserBackendConnection::NetHsm {
1547 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1548 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1549 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1550 },
1551 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1552 connections: BTreeSet::from_iter([
1553 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1554 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1555 ]),
1556 mapping: NetHsmUserMapping::Signing {
1557 backend_user: "signing".parse()?,
1558 signing_key_id: "signing1".parse()?,
1559 key_setup: SigningKeySetup::new(
1560 KeyType::Curve25519,
1561 vec![KeyMechanism::EdDsaSignature],
1562 None,
1563 SignatureType::EdDsa,
1564 CryptographicKeyContext::OpenPgp {
1565 user_ids: OpenPgpUserIdList::new(vec![
1566 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1567 ])?,
1568 notations: Default::default(),
1569 version: "v4".parse()?,
1570 },
1571 )?,
1572 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1573 system_user: "nethsm-signing".parse()?,
1574 tag: "signing1".to_string(),
1575 }
1576 },
1577 ],
1578 )]
1579 #[case::filter_admin(
1580 &[UserBackendConnectionFilter::Admin],
1581 vec![
1582 UserBackendConnection::NetHsm {
1583 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1584 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1585 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1586 },
1587 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1588 connections: BTreeSet::from_iter([
1589 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1590 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1591 ]),
1592 mapping: NetHsmUserMapping::Admin("admin".parse()?)
1593 },
1594 ],
1595 )]
1596 #[case::filter_non_admin(
1597 &[UserBackendConnectionFilter::NonAdmin],
1598 vec![
1599 UserBackendConnection::NetHsm {
1600 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1601 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1602 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1603 },
1604 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1605 connections: BTreeSet::from_iter([
1606 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1607 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1608 ]),
1609 mapping: NetHsmUserMapping::Backup{
1610 backend_user: "backup".parse()?,
1611 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1612 system_user: "nethsm-backup".parse()?,
1613 }
1614 },
1615 UserBackendConnection::NetHsm {
1616 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1617 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1618 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1619 },
1620 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1621 connections: BTreeSet::from_iter([
1622 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1623 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1624 ]),
1625 mapping: NetHsmUserMapping::HermeticMetrics {
1626 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1627 system_user: "nethsm-hermetic-metrics".parse()?,
1628 }
1629 },
1630 UserBackendConnection::NetHsm {
1631 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1632 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1633 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1634 },
1635 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1636 connections: BTreeSet::from_iter([
1637 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1638 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1639 ]),
1640 mapping: NetHsmUserMapping::Metrics {
1641 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1642 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1643 system_user: "nethsm-metrics".parse()?,
1644 }
1645 },
1646 UserBackendConnection::NetHsm {
1647 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
1648 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1649 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1650 },
1651 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
1652 connections: BTreeSet::from_iter([
1653 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
1654 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
1655 ]),
1656 mapping: NetHsmUserMapping::Signing {
1657 backend_user: "signing".parse()?,
1658 signing_key_id: "signing1".parse()?,
1659 key_setup: SigningKeySetup::new(
1660 KeyType::Curve25519,
1661 vec![KeyMechanism::EdDsaSignature],
1662 None,
1663 SignatureType::EdDsa,
1664 CryptographicKeyContext::OpenPgp {
1665 user_ids: OpenPgpUserIdList::new(vec![
1666 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1667 ])?,
1668 notations: Default::default(),
1669 version: "v4".parse()?,
1670 },
1671 )?,
1672 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1673 system_user: "nethsm-signing".parse()?,
1674 tag: "signing1".to_string(),
1675 }
1676 },
1677 ],
1678 )]
1679 fn config_user_backend_connections(
1680 default_config: TestResult<Config>,
1681 #[case] filters: &[UserBackendConnectionFilter],
1682 #[case] expected_connections: Vec<UserBackendConnection>,
1683 ) -> TestResult {
1684 let config = default_config?;
1685
1686 assert_eq!(
1687 expected_connections,
1688 config.user_backend_connections(filters)
1689 );
1690
1691 Ok(())
1692 }
1693
1694 #[rstest]
1697 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
1698 let config = default_config?;
1699 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
1700 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
1701 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
1702 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
1703 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1704 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
1705 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
1706 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
1707 ]);
1708
1709 assert_eq!(
1710 config.authorized_key_entries(),
1711 expected.iter().collect::<HashSet<_>>()
1712 );
1713 Ok(())
1714 }
1715
1716 #[rstest]
1718 fn config_system_user_data(
1719 default_config: TestResult<Config>,
1720 raw_user_data: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1721 ) -> TestResult {
1722 let config = default_config?;
1723 let raw_user_data = raw_user_data?;
1724 let expected: HashSet<SystemUserData> = HashSet::from_iter([
1725 SystemUserData::HostShareholder {
1726 system_user: &raw_user_data[0].0,
1727 ssh_authorized_key: raw_user_data[0]
1728 .1
1729 .as_ref()
1730 .expect("to have SSH authorized key"),
1731 },
1732 SystemUserData::HostShareholder {
1733 system_user: &raw_user_data[1].0,
1734 ssh_authorized_key: raw_user_data[1]
1735 .1
1736 .as_ref()
1737 .expect("to have SSH authorized key"),
1738 },
1739 SystemUserData::HostShareholder {
1740 system_user: &raw_user_data[2].0,
1741 ssh_authorized_key: raw_user_data[2]
1742 .1
1743 .as_ref()
1744 .expect("to have SSH authorized key"),
1745 },
1746 SystemUserData::HostDownloadNetworkConfig {
1747 system_user: &raw_user_data[3].0,
1748 ssh_authorized_key: raw_user_data[3]
1749 .1
1750 .as_ref()
1751 .expect("to have SSH authorized key"),
1752 },
1753 SystemUserData::BackendAdmin {
1754 system_user: raw_user_data[4].0.clone(),
1755 },
1756 SystemUserData::BackendBackup {
1757 system_user: &raw_user_data[5].0,
1758 ssh_authorized_key: raw_user_data[5]
1759 .1
1760 .as_ref()
1761 .expect("to have SSH authorized key"),
1762 },
1763 SystemUserData::BackendHermeticMetrics {
1764 system_user: &raw_user_data[6].0,
1765 },
1766 SystemUserData::BackendMetrics {
1767 system_user: &raw_user_data[7].0,
1768 ssh_authorized_key: raw_user_data[7]
1769 .1
1770 .as_ref()
1771 .expect("to have SSH authorized key"),
1772 },
1773 SystemUserData::BackendSign {
1774 system_user: &raw_user_data[8].0,
1775 ssh_authorized_key: raw_user_data[8]
1776 .1
1777 .as_ref()
1778 .expect("to have SSH authorized key"),
1779 },
1780 ]);
1781
1782 assert_eq!(config.system_user_data(), expected);
1783 Ok(())
1784 }
1785
1786 #[rstest]
1788 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
1789 let config = default_config?;
1790 let expected: HashSet<SystemUserId> = HashSet::from_iter([
1791 "signstar-share-holder1".parse()?,
1792 "signstar-share-holder2".parse()?,
1793 "signstar-share-holder3".parse()?,
1794 "signstar-wireguard-download".parse()?,
1795 "nethsm-backup".parse()?,
1796 "nethsm-hermetic-metrics".parse()?,
1797 "nethsm-metrics".parse()?,
1798 "nethsm-signing".parse()?,
1799 ]);
1800
1801 assert_eq!(
1802 config.system_user_ids(),
1803 expected.iter().collect::<HashSet<_>>()
1804 );
1805 Ok(())
1806 }
1807
1808 #[rstest]
1812 fn config_to_yaml_string(
1813 default_system_config: TestResult<SystemConfig>,
1814 default_nethsm_config: TestResult<NetHsmConfig>,
1815 ) -> TestResult {
1816 let config = ConfigBuilder::new(default_system_config?)
1817 .set_nethsm_config(default_nethsm_config?)
1818 .finish()?;
1819 let config_str = config.to_yaml_string()?;
1820
1821 with_settings!({
1822 description => "Configuration with system-wide and NetHSM configuration",
1823 snapshot_path => SNAPSHOT_PATH,
1824 prepend_module_to_snapshot => false,
1825 }, {
1826 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
1827 });
1828
1829 Ok(())
1830 }
1831
1832 #[rstest]
1837 fn roundtrip_yaml_config(
1838 #[files("../fixtures/config/nethsm_backend/*.yaml")] path: PathBuf,
1839 ) -> TestResult {
1840 let config_string = read_to_string(&path)?;
1841 let config = Config::from_file_path(&path)?;
1842
1843 assert_eq!(config.to_yaml_string()?, config_string);
1844
1845 Ok(())
1846 }
1847
1848 #[rstest]
1852 fn user_backend_connection_secret_handling(
1853 default_config: TestResult<Config>,
1854 ) -> TestResult {
1855 let config = default_config?;
1856 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
1857 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
1858 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
1859 };
1860 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
1861
1862 let user_backend_connection = config
1863 .user_backend_connection(&"nethsm-signing".parse()?)
1864 .expect("there to be a mapping of the requested name");
1865
1866 assert_eq!(
1867 user_backend_connection.admin_secret_handling(),
1868 admin_secret_handling
1869 );
1870 assert_eq!(
1871 user_backend_connection.non_admin_secret_handling(),
1872 non_admin_secret_handling
1873 );
1874
1875 Ok(())
1876 }
1877
1878 #[rstest]
1880 fn system_user_config_state_from_config(default_config: TestResult<Config>) -> TestResult {
1881 let config = default_config?;
1882 let state = SystemUserConfigState::from(&config);
1883
1884 assert_eq!(state.system_user_data, config.system_user_data(),);
1885 Ok(())
1886 }
1887 }
1888
1889 #[cfg(all(feature = "yubihsm2", not(feature = "nethsm")))]
1891 mod yubihsm2_backend {
1892 use pretty_assertions::assert_eq;
1893
1894 use super::*;
1895 use crate::config::{
1896 SystemUserData,
1897 traits::{ConfigSystemUserData, MappingAuthorizedKeyEntry, MappingSystemUserId},
1898 };
1899
1900 #[fixture]
1902 fn default_config(
1903 default_system_config: TestResult<SystemConfig>,
1904 default_yubihsm2_config: TestResult<YubiHsm2Config>,
1905 ) -> TestResult<Config> {
1906 Ok(ConfigBuilder::new(default_system_config?)
1907 .set_yubihsm2_config(default_yubihsm2_config?)
1908 .finish()?)
1909 }
1910
1911 #[fixture]
1914 fn raw_user_data(
1915 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1916 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1917 ) -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
1918 let mut data = raw_user_data_system?;
1919 data.extend(raw_user_data_yubihsm2?);
1920 Ok(data)
1921 }
1922
1923 #[rstest]
1925 fn user_backend_connection_system_user_id(
1926 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1927 ) -> TestResult {
1928 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
1929 let data = UserBackendConnection::YubiHsm2 {
1930 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1931 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1932 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
1933 serial_number: "0123456789".parse()?,
1934 }]),
1935 mapping: YubiHsm2UserMapping::AuditLog {
1936 authentication_key_id: "1".parse()?,
1937 ssh_authorized_key: raw_user_data_yubihsm2[1]
1938 .1
1939 .clone()
1940 .expect("to have an SSH authorized key"),
1941 system_user: raw_user_data_yubihsm2[1].0.clone(),
1942 },
1943 };
1944 assert_eq!(data.system_user_id(), Some(&raw_user_data_yubihsm2[1].0));
1945
1946 Ok(())
1947 }
1948
1949 #[rstest]
1952 fn user_backend_connection_authorized_key_entry(
1953 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
1954 ) -> TestResult {
1955 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
1956 let data = UserBackendConnection::YubiHsm2 {
1957 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
1958 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
1959 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
1960 serial_number: "0123456789".parse()?,
1961 }]),
1962 mapping: YubiHsm2UserMapping::AuditLog {
1963 authentication_key_id: "1".parse()?,
1964 ssh_authorized_key: raw_user_data_yubihsm2[1]
1965 .1
1966 .clone()
1967 .expect("to have an SSH authorized key"),
1968 system_user: raw_user_data_yubihsm2[1].0.clone(),
1969 },
1970 };
1971 assert_eq!(
1972 data.authorized_key_entry(),
1973 Some(
1974 raw_user_data_yubihsm2[1]
1975 .1
1976 .as_ref()
1977 .expect("to have an SSH authorized key")
1978 )
1979 );
1980
1981 Ok(())
1982 }
1983
1984 #[rstest]
1990 #[case::two_duplicate_system_users_two_duplicate_ssh_public_keys(
1991 "Configuration with system-wide and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys",
1992 YubiHsm2Config::new(
1993 BTreeSet::from_iter([
1994 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
1995 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
1996 ]),
1997 BTreeSet::from_iter([
1998 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
1999 YubiHsm2UserMapping::AuditLog {
2000 authentication_key_id: "3".parse()?,
2001 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2002 system_user: "signstar-share-holder2".parse()?,
2003 },
2004 YubiHsm2UserMapping::Backup{
2005 authentication_key_id: "2".parse()?,
2006 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2007 system_user: "signstar-share-holder1".parse()?,
2008 },
2009 YubiHsm2UserMapping::HermeticAuditLog {
2010 authentication_key_id: "4".parse()?,
2011 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2012 },
2013 YubiHsm2UserMapping::Signing {
2014 authentication_key_id: "5".parse()?,
2015 signing_key_id: "1".parse()?,
2016 key_setup: SigningKeySetup::new(
2017 KeyType::Curve25519,
2018 vec![KeyMechanism::EdDsaSignature],
2019 None,
2020 SignatureType::EdDsa,
2021 CryptographicKeyContext::OpenPgp {
2022 user_ids: OpenPgpUserIdList::new(vec![
2023 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2024 ])?,
2025 notations: Default::default(),
2026 version: "v4".parse()?,
2027 },
2028 )?,
2029 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2030 system_user: "yubihsm2-signing".parse()?,
2031 domain: Domain::One,
2032 }
2033 ]),
2034 )?
2035 )]
2036 #[case::one_duplicate_system_user_two_duplicate_ssh_public_keys(
2037 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user and two duplicate SSH public keys",
2038 YubiHsm2Config::new(
2039 BTreeSet::from_iter([
2040 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2041 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2042 ]),
2043 BTreeSet::from_iter([
2044 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2045 YubiHsm2UserMapping::AuditLog {
2046 authentication_key_id: "3".parse()?,
2047 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2048 system_user: "yubihsm2-audit-log".parse()?,
2049 },
2050 YubiHsm2UserMapping::Backup{
2051 authentication_key_id: "2".parse()?,
2052 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2053 system_user: "signstar-share-holder1".parse()?,
2054 },
2055 YubiHsm2UserMapping::HermeticAuditLog {
2056 authentication_key_id: "4".parse()?,
2057 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2058 },
2059 YubiHsm2UserMapping::Signing {
2060 authentication_key_id: "5".parse()?,
2061 signing_key_id: "1".parse()?,
2062 key_setup: SigningKeySetup::new(
2063 KeyType::Curve25519,
2064 vec![KeyMechanism::EdDsaSignature],
2065 None,
2066 SignatureType::EdDsa,
2067 CryptographicKeyContext::OpenPgp {
2068 user_ids: OpenPgpUserIdList::new(vec![
2069 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2070 ])?,
2071 notations: Default::default(),
2072 version: "v4".parse()?,
2073 },
2074 )?,
2075 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2076 system_user: "yubihsm2-signing".parse()?,
2077 domain: Domain::One,
2078 }
2079 ]),
2080 )?
2081 )]
2082 #[case::one_duplicate_system_user_one_duplicate_ssh_public_key(
2083 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user and one duplicate SSH public key",
2084 YubiHsm2Config::new(
2085 BTreeSet::from_iter([
2086 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2087 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2088 ]),
2089 BTreeSet::from_iter([
2090 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2091 YubiHsm2UserMapping::AuditLog {
2092 authentication_key_id: "3".parse()?,
2093 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2094 system_user: "yubihsm2-audit-log".parse()?,
2095 },
2096 YubiHsm2UserMapping::Backup{
2097 authentication_key_id: "2".parse()?,
2098 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2099 system_user: "signstar-share-holder1".parse()?,
2100 },
2101 YubiHsm2UserMapping::HermeticAuditLog {
2102 authentication_key_id: "4".parse()?,
2103 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2104 },
2105 YubiHsm2UserMapping::Signing {
2106 authentication_key_id: "5".parse()?,
2107 signing_key_id: "1".parse()?,
2108 key_setup: SigningKeySetup::new(
2109 KeyType::Curve25519,
2110 vec![KeyMechanism::EdDsaSignature],
2111 None,
2112 SignatureType::EdDsa,
2113 CryptographicKeyContext::OpenPgp {
2114 user_ids: OpenPgpUserIdList::new(vec![
2115 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2116 ])?,
2117 notations: Default::default(),
2118 version: "v4".parse()?,
2119 },
2120 )?,
2121 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2122 system_user: "yubihsm2-signing".parse()?,
2123 domain: Domain::One,
2124 }
2125 ]),
2126 )?
2127 )]
2128 #[case::one_duplicate_ssh_public_key(
2129 "Configuration with system-wide and YubiHSM2 configuration has one duplicate SSH public key",
2130 YubiHsm2Config::new(
2131 BTreeSet::from_iter([
2132 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2133 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2134 ]),
2135 BTreeSet::from_iter([
2136 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2137 YubiHsm2UserMapping::AuditLog {
2138 authentication_key_id: "3".parse()?,
2139 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2140 system_user: "yubihsm2-audit-log".parse()?,
2141 },
2142 YubiHsm2UserMapping::Backup{
2143 authentication_key_id: "2".parse()?,
2144 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2145 system_user: "yubihsm2-backup".parse()?,
2146 },
2147 YubiHsm2UserMapping::HermeticAuditLog {
2148 authentication_key_id: "4".parse()?,
2149 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2150 },
2151 YubiHsm2UserMapping::Signing {
2152 authentication_key_id: "5".parse()?,
2153 signing_key_id: "1".parse()?,
2154 key_setup: SigningKeySetup::new(
2155 KeyType::Curve25519,
2156 vec![KeyMechanism::EdDsaSignature],
2157 None,
2158 SignatureType::EdDsa,
2159 CryptographicKeyContext::OpenPgp {
2160 user_ids: OpenPgpUserIdList::new(vec![
2161 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2162 ])?,
2163 notations: Default::default(),
2164 version: "v4".parse()?,
2165 },
2166 )?,
2167 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2168 system_user: "yubihsm2-signing".parse()?,
2169 domain: Domain::One,
2170 }
2171 ]),
2172 )?
2173 )]
2174 #[case::one_duplicate_system_user(
2175 "Configuration with system-wide and YubiHSM2 configuration has one duplicate system user",
2176 YubiHsm2Config::new(
2177 BTreeSet::from_iter([
2178 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2179 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2180 ]),
2181 BTreeSet::from_iter([
2182 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2183 YubiHsm2UserMapping::AuditLog {
2184 authentication_key_id: "3".parse()?,
2185 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2186 system_user: "yubihsm2-audit-log".parse()?,
2187 },
2188 YubiHsm2UserMapping::Backup{
2189 authentication_key_id: "2".parse()?,
2190 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2191 system_user: "signstar-share-holder1".parse()?,
2192 },
2193 YubiHsm2UserMapping::HermeticAuditLog {
2194 authentication_key_id: "4".parse()?,
2195 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2196 },
2197 YubiHsm2UserMapping::Signing {
2198 authentication_key_id: "5".parse()?,
2199 signing_key_id: "1".parse()?,
2200 key_setup: SigningKeySetup::new(
2201 KeyType::Curve25519,
2202 vec![KeyMechanism::EdDsaSignature],
2203 None,
2204 SignatureType::EdDsa,
2205 CryptographicKeyContext::OpenPgp {
2206 user_ids: OpenPgpUserIdList::new(vec![
2207 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2208 ])?,
2209 notations: Default::default(),
2210 version: "v4".parse()?,
2211 },
2212 )?,
2213 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2214 system_user: "yubihsm2-signing".parse()?,
2215 domain: Domain::One,
2216 }
2217 ]),
2218 )?
2219 )]
2220 fn config_builder_fails_validation(
2221 default_system_config: TestResult<SystemConfig>,
2222 #[case] description: &str,
2223 #[case] yubihsm2_config: YubiHsm2Config,
2224 ) -> TestResult {
2225 let error_message = match ConfigBuilder::new(default_system_config?)
2226 .set_yubihsm2_config(yubihsm2_config)
2227 .finish()
2228 {
2229 Err(error) => error.to_string(),
2230 Ok(config) => panic!(
2231 "Expected to fail with Error::Validation, but succeeded instead: {}",
2232 config.to_yaml_string()?
2233 ),
2234 };
2235
2236 with_settings!({
2237 description => description,
2238 snapshot_path => SNAPSHOT_PATH,
2239 prepend_module_to_snapshot => false,
2240 }, {
2241 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
2242 });
2243
2244 Ok(())
2245 }
2246
2247 #[rstest]
2249 fn config_yubihsm2(
2250 default_system_config: TestResult<SystemConfig>,
2251 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2252 ) -> TestResult {
2253 let yubihsm2_config = default_yubihsm2_config?;
2254
2255 let config = ConfigBuilder::new(default_system_config?)
2256 .set_yubihsm2_config(yubihsm2_config.clone())
2257 .finish()?;
2258
2259 assert_eq!(
2260 &yubihsm2_config,
2261 config.yubihsm2().expect("a YubiHsm2Config reference")
2262 );
2263
2264 Ok(())
2265 }
2266
2267 #[rstest]
2269 #[case::yubihsm2_signing(
2270 "yubihsm2-signing",
2271 Some(UserBackendConnection::YubiHsm2 {
2272 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2273 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2274 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2275 },
2276 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2277 connections: BTreeSet::from_iter([
2278 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2279 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2280 ]),
2281 mapping: YubiHsm2UserMapping::Signing {
2282 authentication_key_id: "5".parse()?,
2283 signing_key_id: "1".parse()?,
2284 key_setup: SigningKeySetup::new(
2285 KeyType::Curve25519,
2286 vec![KeyMechanism::EdDsaSignature],
2287 None,
2288 SignatureType::EdDsa,
2289 CryptographicKeyContext::OpenPgp {
2290 user_ids: OpenPgpUserIdList::new(vec![
2291 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2292 ])?,
2293 notations: Default::default(),
2294 version: "v4".parse()?,
2295 },
2296 )?,
2297 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2298 system_user: "yubihsm2-signing".parse()?,
2299 domain: Domain::One,
2300 }
2301 })
2302 )]
2303 #[case::none("foo", None)]
2304 fn config_user_backend_connection(
2305 default_config: TestResult<Config>,
2306 #[case] system_user: &str,
2307 #[case] expected_connection: Option<UserBackendConnection>,
2308 ) -> TestResult {
2309 let config = default_config?;
2310 assert_eq!(
2311 expected_connection,
2312 config.user_backend_connection(&system_user.parse()?)
2313 );
2314
2315 Ok(())
2316 }
2317
2318 #[rstest]
2321 #[case::no_filter(
2322 &[],
2323 vec![
2324 UserBackendConnection::YubiHsm2 {
2325 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2326 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2327 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2328 },
2329 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2330 connections: BTreeSet::from_iter([
2331 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2332 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2333 ]),
2334 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2335 },
2336 UserBackendConnection::YubiHsm2 {
2337 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2338 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2339 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2340 },
2341 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2342 connections: BTreeSet::from_iter([
2343 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2344 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2345 ]),
2346 mapping: YubiHsm2UserMapping::AuditLog {
2347 authentication_key_id: "3".parse()?,
2348 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2349 system_user: "yubihsm2-audit-log".parse()?,
2350 },
2351 },
2352 UserBackendConnection::YubiHsm2 {
2353 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2354 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2355 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2356 },
2357 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2358 connections: BTreeSet::from_iter([
2359 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2360 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2361 ]),
2362 mapping: YubiHsm2UserMapping::Backup{
2363 authentication_key_id: "2".parse()?,
2364 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2365 system_user: "yubihsm2-backup".parse()?,
2366 },
2367 },
2368 UserBackendConnection::YubiHsm2 {
2369 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2370 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2371 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2372 },
2373 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2374 connections: BTreeSet::from_iter([
2375 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2376 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2377 ]),
2378 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2379 authentication_key_id: "4".parse()?,
2380 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2381 },
2382 },
2383 UserBackendConnection::YubiHsm2 {
2384 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2385 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2386 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2387 },
2388 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2389 connections: BTreeSet::from_iter([
2390 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2391 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2392 ]),
2393 mapping: YubiHsm2UserMapping::Signing {
2394 authentication_key_id: "5".parse()?,
2395 signing_key_id: "1".parse()?,
2396 key_setup: SigningKeySetup::new(
2397 KeyType::Curve25519,
2398 vec![KeyMechanism::EdDsaSignature],
2399 None,
2400 SignatureType::EdDsa,
2401 CryptographicKeyContext::OpenPgp {
2402 user_ids: OpenPgpUserIdList::new(vec![
2403 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2404 ])?,
2405 notations: Default::default(),
2406 version: "v4".parse()?,
2407 },
2408 )?,
2409 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2410 system_user: "yubihsm2-signing".parse()?,
2411 domain: Domain::One,
2412 }
2413 },
2414 ],
2415 )]
2416 #[case::filter_admin(
2417 &[UserBackendConnectionFilter::Admin],
2418 vec![
2419 UserBackendConnection::YubiHsm2 {
2420 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2421 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2422 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2423 },
2424 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2425 connections: BTreeSet::from_iter([
2426 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2427 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2428 ]),
2429 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2430 },
2431 ],
2432 )]
2433 #[case::filter_non_admin(
2434 &[UserBackendConnectionFilter::NonAdmin],
2435 vec![
2436 UserBackendConnection::YubiHsm2 {
2437 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2438 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2439 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2440 },
2441 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2442 connections: BTreeSet::from_iter([
2443 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2444 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2445 ]),
2446 mapping: YubiHsm2UserMapping::AuditLog {
2447 authentication_key_id: "3".parse()?,
2448 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2449 system_user: "yubihsm2-audit-log".parse()?,
2450 },
2451 },
2452 UserBackendConnection::YubiHsm2 {
2453 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2454 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2455 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2456 },
2457 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2458 connections: BTreeSet::from_iter([
2459 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2460 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2461 ]),
2462 mapping: YubiHsm2UserMapping::Backup{
2463 authentication_key_id: "2".parse()?,
2464 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2465 system_user: "yubihsm2-backup".parse()?,
2466 },
2467 },
2468 UserBackendConnection::YubiHsm2 {
2469 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2470 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2471 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2472 },
2473 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2474 connections: BTreeSet::from_iter([
2475 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2476 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2477 ]),
2478 mapping: YubiHsm2UserMapping::HermeticAuditLog {
2479 authentication_key_id: "4".parse()?,
2480 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2481 },
2482 },
2483 UserBackendConnection::YubiHsm2 {
2484 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
2485 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2486 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2487 },
2488 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
2489 connections: BTreeSet::from_iter([
2490 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2491 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2492 ]),
2493 mapping: YubiHsm2UserMapping::Signing {
2494 authentication_key_id: "5".parse()?,
2495 signing_key_id: "1".parse()?,
2496 key_setup: SigningKeySetup::new(
2497 KeyType::Curve25519,
2498 vec![KeyMechanism::EdDsaSignature],
2499 None,
2500 SignatureType::EdDsa,
2501 CryptographicKeyContext::OpenPgp {
2502 user_ids: OpenPgpUserIdList::new(vec![
2503 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2504 ])?,
2505 notations: Default::default(),
2506 version: "v4".parse()?,
2507 },
2508 )?,
2509 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2510 system_user: "yubihsm2-signing".parse()?,
2511 domain: Domain::One,
2512 }
2513 },
2514 ],
2515 )]
2516 fn config_user_backend_connections(
2517 default_config: TestResult<Config>,
2518 #[case] filters: &[UserBackendConnectionFilter],
2519 #[case] expected_connections: Vec<UserBackendConnection>,
2520 ) -> TestResult {
2521 let config = default_config?;
2522
2523 assert_eq!(
2524 expected_connections,
2525 config.user_backend_connections(filters)
2526 );
2527
2528 Ok(())
2529 }
2530
2531 #[rstest]
2535 fn config_to_yaml_string(
2536 default_system_config: TestResult<SystemConfig>,
2537 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2538 ) -> TestResult {
2539 let config = ConfigBuilder::new(default_system_config?)
2540 .set_yubihsm2_config(default_yubihsm2_config?)
2541 .finish()?;
2542 let config_str = config.to_yaml_string()?;
2543
2544 with_settings!({
2545 description => "Configuration with system-wide and YubiHSM2 configuration",
2546 snapshot_path => SNAPSHOT_PATH,
2547 prepend_module_to_snapshot => false,
2548 }, {
2549 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
2550 });
2551
2552 Ok(())
2553 }
2554
2555 #[rstest]
2558 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
2559 let config = default_config?;
2560 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
2561 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
2562 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2563 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
2564 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2565 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2566 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
2567 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2568 ]);
2569
2570 assert_eq!(
2571 config.authorized_key_entries(),
2572 expected.iter().collect::<HashSet<_>>()
2573 );
2574 Ok(())
2575 }
2576
2577 #[rstest]
2579 fn config_system_user_data(
2580 default_config: TestResult<Config>,
2581 raw_user_data: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2582 ) -> TestResult {
2583 let config = default_config?;
2584 let raw_user_data = raw_user_data?;
2585 let expected: HashSet<SystemUserData> = HashSet::from_iter([
2586 SystemUserData::HostShareholder {
2587 system_user: &raw_user_data[0].0,
2588 ssh_authorized_key: raw_user_data[0]
2589 .1
2590 .as_ref()
2591 .expect("to have SSH authorized key"),
2592 },
2593 SystemUserData::HostShareholder {
2594 system_user: &raw_user_data[1].0,
2595 ssh_authorized_key: raw_user_data[1]
2596 .1
2597 .as_ref()
2598 .expect("to have SSH authorized key"),
2599 },
2600 SystemUserData::HostShareholder {
2601 system_user: &raw_user_data[2].0,
2602 ssh_authorized_key: raw_user_data[2]
2603 .1
2604 .as_ref()
2605 .expect("to have SSH authorized key"),
2606 },
2607 SystemUserData::HostDownloadNetworkConfig {
2608 system_user: &raw_user_data[3].0,
2609 ssh_authorized_key: raw_user_data[3]
2610 .1
2611 .as_ref()
2612 .expect("to have SSH authorized key"),
2613 },
2614 SystemUserData::BackendAdmin {
2615 system_user: raw_user_data[4].0.clone(),
2616 },
2617 SystemUserData::BackendMetrics {
2618 system_user: &raw_user_data[5].0,
2619 ssh_authorized_key: raw_user_data[5]
2620 .1
2621 .as_ref()
2622 .expect("to have SSH authorized key"),
2623 },
2624 SystemUserData::BackendBackup {
2625 system_user: &raw_user_data[6].0,
2626 ssh_authorized_key: raw_user_data[6]
2627 .1
2628 .as_ref()
2629 .expect("to have SSH authorized key"),
2630 },
2631 SystemUserData::BackendHermeticMetrics {
2632 system_user: &raw_user_data[7].0,
2633 },
2634 SystemUserData::BackendSign {
2635 system_user: &raw_user_data[8].0,
2636 ssh_authorized_key: raw_user_data[8]
2637 .1
2638 .as_ref()
2639 .expect("to have SSH authorized key"),
2640 },
2641 ]);
2642
2643 assert_eq!(config.system_user_data(), expected);
2644 Ok(())
2645 }
2646
2647 #[rstest]
2649 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
2650 let config = default_config?;
2651 let expected: HashSet<SystemUserId> = HashSet::from_iter([
2652 "signstar-share-holder1".parse()?,
2653 "signstar-share-holder2".parse()?,
2654 "signstar-share-holder3".parse()?,
2655 "signstar-wireguard-download".parse()?,
2656 "yubihsm2-audit-log".parse()?,
2657 "yubihsm2-backup".parse()?,
2658 "yubihsm2-hermetic-audit-log".parse()?,
2659 "yubihsm2-signing".parse()?,
2660 ]);
2661
2662 assert_eq!(
2663 config.system_user_ids(),
2664 expected.iter().collect::<HashSet<_>>()
2665 );
2666 Ok(())
2667 }
2668
2669 #[rstest]
2674 #[cfg(not(feature = "_yubihsm2-mockhsm"))]
2675 fn roundtrip_yaml_config(
2676 #[files("../fixtures/config/yubihsm2_backend/*.yaml")] path: PathBuf,
2677 ) -> TestResult {
2678 let config_string = read_to_string(&path)?;
2679 let config = Config::from_file_path(&path)?;
2680
2681 assert_eq!(config.to_yaml_string()?, config_string);
2682
2683 Ok(())
2684 }
2685
2686 #[rstest]
2691 #[cfg(feature = "_yubihsm2-mockhsm")]
2692 fn roundtrip_yaml_config_mockhsm(
2693 #[files("../fixtures/config/yubihsm2_mockhsm_backend/*.yaml")] path: PathBuf,
2694 ) -> TestResult {
2695 let config_string = read_to_string(&path)?;
2696 let config = Config::from_file_path(&path)?;
2697
2698 assert_eq!(config.to_yaml_string()?, config_string);
2699
2700 Ok(())
2701 }
2702
2703 #[rstest]
2707 fn user_backend_connection_secret_handling(
2708 default_config: TestResult<Config>,
2709 ) -> TestResult {
2710 let config = default_config?;
2711 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
2712 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
2713 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
2714 };
2715 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
2716
2717 let user_backend_connection = config
2718 .user_backend_connection(&"yubihsm2-signing".parse()?)
2719 .expect("there to be a mapping of the requested name");
2720
2721 assert_eq!(
2722 user_backend_connection.admin_secret_handling(),
2723 admin_secret_handling
2724 );
2725 assert_eq!(
2726 user_backend_connection.non_admin_secret_handling(),
2727 non_admin_secret_handling
2728 );
2729
2730 Ok(())
2731 }
2732
2733 #[rstest]
2735 fn system_user_config_state_from_config(default_config: TestResult<Config>) -> TestResult {
2736 let config = default_config?;
2737 let state = SystemUserConfigState::from(&config);
2738
2739 assert_eq!(state.system_user_data, config.system_user_data(),);
2740 Ok(())
2741 }
2742 }
2743
2744 #[cfg(all(feature = "nethsm", feature = "yubihsm2"))]
2746 mod all_backends {
2747 use log::LevelFilter;
2748 use pretty_assertions::assert_eq;
2749 use signstar_common::logging::setup_terminal_logging;
2750
2751 use super::*;
2752 use crate::config::{
2753 MappingAuthorizedKeyEntry,
2754 MappingSystemUserId,
2755 SystemUserData,
2756 traits::ConfigSystemUserData,
2757 };
2758
2759 #[fixture]
2761 fn default_config(
2762 default_system_config: TestResult<SystemConfig>,
2763 default_nethsm_config: TestResult<NetHsmConfig>,
2764 default_yubihsm2_config: TestResult<YubiHsm2Config>,
2765 ) -> TestResult<Config> {
2766 Ok(ConfigBuilder::new(default_system_config?)
2767 .set_nethsm_config(default_nethsm_config?)
2768 .set_yubihsm2_config(default_yubihsm2_config?)
2769 .finish()?)
2770 }
2771
2772 #[fixture]
2775 fn raw_user_data(
2776 raw_user_data_system: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2777 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2778 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2779 ) -> TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>> {
2780 let mut data = raw_user_data_system?;
2781 data.extend(raw_user_data_nethsm?);
2782 data.extend(raw_user_data_yubihsm2?);
2783 Ok(data)
2784 }
2785
2786 #[rstest]
2788 fn user_backend_connection_system_user_id(
2789 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2790 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2791 ) -> TestResult {
2792 let raw_user_data_nethsm = raw_user_data_nethsm?;
2793 let data = UserBackendConnection::NetHsm {
2794 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2795 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2796 connections: BTreeSet::from_iter([Connection::new(
2797 "https://nethsm1.example.org/".parse()?,
2798 ConnectionSecurity::Unsafe,
2799 )]),
2800 mapping: NetHsmUserMapping::Backup {
2801 backend_user: "backup".parse()?,
2802 ssh_authorized_key: raw_user_data_nethsm[1]
2803 .1
2804 .clone()
2805 .expect("to have an SSH authorized key"),
2806 system_user: raw_user_data_nethsm[1].0.clone(),
2807 },
2808 };
2809 assert_eq!(data.system_user_id(), Some(&raw_user_data_nethsm[1].0));
2810
2811 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
2812 let data = UserBackendConnection::YubiHsm2 {
2813 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2814 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2815 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
2816 serial_number: "0123456789".parse()?,
2817 }]),
2818 mapping: YubiHsm2UserMapping::AuditLog {
2819 authentication_key_id: "1".parse()?,
2820 ssh_authorized_key: raw_user_data_yubihsm2[1]
2821 .1
2822 .clone()
2823 .expect("to have an SSH authorized key"),
2824 system_user: raw_user_data_yubihsm2[1].0.clone(),
2825 },
2826 };
2827 assert_eq!(data.system_user_id(), Some(&raw_user_data_yubihsm2[1].0));
2828
2829 Ok(())
2830 }
2831
2832 #[rstest]
2835 fn user_backend_connection_authorized_key_entry(
2836 raw_user_data_nethsm: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2837 raw_user_data_yubihsm2: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
2838 ) -> TestResult {
2839 let raw_user_data_nethsm = raw_user_data_nethsm?;
2840 let data = UserBackendConnection::NetHsm {
2841 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2842 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2843 connections: BTreeSet::from_iter([Connection::new(
2844 "https://nethsm1.example.org/".parse()?,
2845 ConnectionSecurity::Unsafe,
2846 )]),
2847 mapping: NetHsmUserMapping::Backup {
2848 backend_user: "backup".parse()?,
2849 ssh_authorized_key: raw_user_data_nethsm[1]
2850 .1
2851 .clone()
2852 .expect("to have an SSH authorized key"),
2853 system_user: raw_user_data_nethsm[1].0.clone(),
2854 },
2855 };
2856 assert_eq!(
2857 data.authorized_key_entry(),
2858 Some(
2859 raw_user_data_nethsm[1]
2860 .1
2861 .as_ref()
2862 .expect("to have an SSH authorized key")
2863 )
2864 );
2865
2866 let raw_user_data_yubihsm2 = raw_user_data_yubihsm2?;
2867 let data = UserBackendConnection::YubiHsm2 {
2868 admin_secret_handling: AdministrativeSecretHandling::Plaintext,
2869 non_admin_secret_handling: NonAdministrativeSecretHandling::Plaintext,
2870 connections: BTreeSet::from_iter([YubiHsm2Connection::Usb {
2871 serial_number: "0123456789".parse()?,
2872 }]),
2873 mapping: YubiHsm2UserMapping::AuditLog {
2874 authentication_key_id: "1".parse()?,
2875 ssh_authorized_key: raw_user_data_yubihsm2[1]
2876 .1
2877 .clone()
2878 .expect("to have an SSH authorized key"),
2879 system_user: raw_user_data_yubihsm2[1].0.clone(),
2880 },
2881 };
2882 assert_eq!(
2883 data.authorized_key_entry(),
2884 Some(
2885 raw_user_data_yubihsm2[1]
2886 .1
2887 .as_ref()
2888 .expect("to have an SSH authorized key")
2889 )
2890 );
2891
2892 Ok(())
2893 }
2894
2895 #[rstest]
2902 #[case::backend_overlap_duplicate_system_users_two_duplicate_ssh_public_keys(
2903 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has two duplicate system users and two duplicate SSH public keys in the backends",
2904 NetHsmConfig::new(
2905 BTreeSet::from_iter([
2906 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2907 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2908 ]),
2909 BTreeSet::from_iter([
2910 NetHsmUserMapping::Admin("admin".parse()?),
2911 NetHsmUserMapping::Backup{
2912 backend_user: "backup".parse()?,
2913 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2914 system_user: "duplicate-backup".parse()?,
2915 },
2916 NetHsmUserMapping::HermeticMetrics {
2917 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2918 system_user: "nethsm-hermetic-metrics".parse()?,
2919 },
2920 NetHsmUserMapping::Metrics {
2921 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2922 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2923 system_user: "duplicate-metrics".parse()?,
2924 },
2925 NetHsmUserMapping::Signing {
2926 backend_user: "signing".parse()?,
2927 signing_key_id: "signing1".parse()?,
2928 key_setup: SigningKeySetup::new(
2929 KeyType::Curve25519,
2930 vec![KeyMechanism::EdDsaSignature],
2931 None,
2932 SignatureType::EdDsa,
2933 CryptographicKeyContext::OpenPgp {
2934 user_ids: OpenPgpUserIdList::new(vec![
2935 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2936 ])?,
2937 version: "v4".parse()?,
2938 notations: Default::default(),
2939 },
2940 )?,
2941 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
2942 system_user: "nethsm-signing".parse()?,
2943 tag: "nethsm-signing1".to_string(),
2944 }
2945 ]),
2946 )?,
2947 YubiHsm2Config::new(
2948 BTreeSet::from_iter([
2949 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
2950 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
2951 ]),
2952 BTreeSet::from_iter([
2953 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
2954 YubiHsm2UserMapping::AuditLog {
2955 authentication_key_id: "3".parse()?,
2956 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
2957 system_user: "duplicate-metrics".parse()?,
2958 },
2959 YubiHsm2UserMapping::Backup {
2960 authentication_key_id: "2".parse()?,
2961 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
2962 system_user: "duplicate-backup".parse()?,
2963 },
2964 YubiHsm2UserMapping::HermeticAuditLog {
2965 authentication_key_id: "4".parse()?,
2966 system_user: "yubihsm2-hermetic-audit-log".parse()?,
2967 },
2968 YubiHsm2UserMapping::Signing {
2969 authentication_key_id: "5".parse()?,
2970 key_setup: SigningKeySetup::new(
2971 KeyType::Curve25519,
2972 vec![KeyMechanism::EdDsaSignature],
2973 None,
2974 SignatureType::EdDsa,
2975 CryptographicKeyContext::OpenPgp {
2976 user_ids: OpenPgpUserIdList::new(vec![
2977 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2978 ])?,
2979 version: "v4".parse()?,
2980 notations: Default::default(),
2981 },
2982 )?,
2983 signing_key_id: "1".parse()?,
2984 domain: Domain::One,
2985 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2986 system_user: "yubihsm2-signing".parse()? }
2987 ]),
2988 )?,
2989 )]
2990 #[case::backend_overlap_one_duplicate_system_user(
2991 "Configuration with system-wide, NetHSM and YubiHSM2 configuration has one duplicate system user in the backends",
2992 NetHsmConfig::new(
2993 BTreeSet::from_iter([
2994 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
2995 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
2996 ]),
2997 BTreeSet::from_iter([
2998 NetHsmUserMapping::Admin("admin".parse()?),
2999 NetHsmUserMapping::Backup{
3000 backend_user: "backup".parse()?,
3001 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3002 system_user: "duplicate-backup".parse()?,
3003 },
3004 NetHsmUserMapping::HermeticMetrics {
3005 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3006 system_user: "nethsm-hermetic-metrics".parse()?,
3007 },
3008 NetHsmUserMapping::Metrics {
3009 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3010 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3011 system_user: "nethsm-metrics".parse()?,
3012 },
3013 NetHsmUserMapping::Signing {
3014 backend_user: "signing".parse()?,
3015 signing_key_id: "signing1".parse()?,
3016 key_setup: SigningKeySetup::new(
3017 KeyType::Curve25519,
3018 vec![KeyMechanism::EdDsaSignature],
3019 None,
3020 SignatureType::EdDsa,
3021 CryptographicKeyContext::OpenPgp {
3022 user_ids: OpenPgpUserIdList::new(vec![
3023 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3024 ])?,
3025 version: "v4".parse()?,
3026 notations: Default::default(),
3027 },
3028 )?,
3029 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3030 system_user: "nethsm-signing".parse()?,
3031 tag: "nethsm-signing1".to_string(),
3032 }
3033 ]),
3034 )?,
3035 YubiHsm2Config::new(
3036 BTreeSet::from_iter([
3037 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3038 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3039 ]),
3040 BTreeSet::from_iter([
3041 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3042 YubiHsm2UserMapping::AuditLog {
3043 authentication_key_id: "3".parse()?,
3044 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3045 system_user: "yubihsm2-audit-log".parse()?,
3046 },
3047 YubiHsm2UserMapping::Backup {
3048 authentication_key_id: "2".parse()?,
3049 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3050 system_user: "duplicate-backup".parse()?,
3051 },
3052 YubiHsm2UserMapping::HermeticAuditLog {
3053 authentication_key_id: "4".parse()?,
3054 system_user: "yubihsm2-hermetic-audit-log".parse()?,
3055 },
3056 YubiHsm2UserMapping::Signing {
3057 authentication_key_id: "5".parse()?,
3058 key_setup: SigningKeySetup::new(
3059 KeyType::Curve25519,
3060 vec![KeyMechanism::EdDsaSignature],
3061 None,
3062 SignatureType::EdDsa,
3063 CryptographicKeyContext::OpenPgp {
3064 user_ids: OpenPgpUserIdList::new(vec![
3065 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3066 ])?,
3067 version: "v4".parse()?,
3068 notations: Default::default(),
3069 },
3070 )?,
3071 signing_key_id: "1".parse()?,
3072 domain: Domain::One,
3073 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3074 system_user: "yubihsm2-signing".parse()? }
3075 ]),
3076 )?,
3077 )]
3078 #[case::system_overlap_duplicate_system_users_two_duplicate_ssh_public_keys(
3079 "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",
3080 NetHsmConfig::new(
3081 BTreeSet::from_iter([
3082 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3083 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3084 ]),
3085 BTreeSet::from_iter([
3086 NetHsmUserMapping::Admin("admin".parse()?),
3087 NetHsmUserMapping::Backup{
3088 backend_user: "backup".parse()?,
3089 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3090 system_user: "duplicate-backup".parse()?,
3091 },
3092 NetHsmUserMapping::Metrics {
3093 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3094 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3095 system_user: "duplicate-metrics".parse()?,
3096 },
3097 NetHsmUserMapping::HermeticMetrics {
3098 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3099 system_user: "nethsm-hermetic-metrics".parse()?,
3100 },
3101 NetHsmUserMapping::Signing {
3102 backend_user: "signing".parse()?,
3103 signing_key_id: "signing1".parse()?,
3104 key_setup: SigningKeySetup::new(
3105 KeyType::Curve25519,
3106 vec![KeyMechanism::EdDsaSignature],
3107 None,
3108 SignatureType::EdDsa,
3109 CryptographicKeyContext::OpenPgp {
3110 user_ids: OpenPgpUserIdList::new(vec![
3111 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3112 ])?,
3113 version: "v4".parse()?,
3114 notations: Default::default(),
3115 },
3116 )?,
3117 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3118 system_user: "nethsm-signing".parse()?,
3119 tag: "nethsm-signing1".to_string(),
3120 }
3121 ]),
3122 )?,
3123 YubiHsm2Config::new(
3124 BTreeSet::from_iter([
3125 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3126 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3127 ]),
3128 BTreeSet::from_iter([
3129 YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3130 YubiHsm2UserMapping::Backup {
3131 authentication_key_id: "2".parse()?,
3132 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3133 system_user: "duplicate-backup".parse()?,
3134 },
3135 YubiHsm2UserMapping::AuditLog {
3136 authentication_key_id: "3".parse()?,
3137 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3138 system_user: "duplicate-metrics".parse()?,
3139 },
3140 YubiHsm2UserMapping::HermeticAuditLog {
3141 authentication_key_id: "4".parse()?,
3142 system_user: "yubihsm2-hermetic-audit-log".parse()?,
3143 },
3144 YubiHsm2UserMapping::Signing {
3145 authentication_key_id: "5".parse()?,
3146 key_setup: SigningKeySetup::new(
3147 KeyType::Curve25519,
3148 vec![KeyMechanism::EdDsaSignature],
3149 None,
3150 SignatureType::EdDsa,
3151 CryptographicKeyContext::OpenPgp {
3152 user_ids: OpenPgpUserIdList::new(vec![
3153 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3154 ])?,
3155 version: "v4".parse()?,
3156 notations: Default::default(),
3157 },
3158 )?,
3159 signing_key_id: "1".parse()?,
3160 domain: Domain::One,
3161 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3162 system_user: "yubihsm2-signing".parse()? }
3163 ]),
3164 )?,
3165 )]
3166 fn config_fails_validation(
3167 default_system_config: TestResult<SystemConfig>,
3168 #[case] description: &str,
3169 #[case] nethsm_config: NetHsmConfig,
3170 #[case] yubihsm2_config: YubiHsm2Config,
3171 ) -> TestResult {
3172 let error_message = match ConfigBuilder::new(default_system_config?)
3173 .set_nethsm_config(nethsm_config)
3174 .set_yubihsm2_config(yubihsm2_config)
3175 .finish()
3176 {
3177 Err(error) => error.to_string(),
3178 Ok(config) => panic!(
3179 "Expected to fail with Error::Validation, but succeeded instead: {}",
3180 config.to_yaml_string()?
3181 ),
3182 };
3183
3184 with_settings!({
3185 description => description,
3186 snapshot_path => SNAPSHOT_PATH,
3187 prepend_module_to_snapshot => false,
3188 }, {
3189 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_message);
3190 });
3191
3192 Ok(())
3193 }
3194
3195 #[rstest]
3197 #[case::nethsm_signing(
3198 "nethsm-signing",
3199 Some(UserBackendConnection::NetHsm {
3200 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3201 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3202 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3203 },
3204 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3205 connections: BTreeSet::from_iter([
3206 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3207 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3208 ]),
3209 mapping: NetHsmUserMapping::Signing {
3210 backend_user: "signing".parse()?,
3211 signing_key_id: "signing1".parse()?,
3212 key_setup: SigningKeySetup::new(
3213 KeyType::Curve25519,
3214 vec![KeyMechanism::EdDsaSignature],
3215 None,
3216 SignatureType::EdDsa,
3217 CryptographicKeyContext::OpenPgp {
3218 user_ids: OpenPgpUserIdList::new(vec![
3219 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3220 ])?,
3221 version: "v4".parse()?,
3222 notations: Default::default(),
3223 },
3224 )?,
3225 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3226 system_user: "nethsm-signing".parse()?,
3227 tag: "signing1".to_string(),
3228 }
3229 })
3230 )]
3231 #[case::yubihsm2_signing(
3232 "yubihsm2-signing",
3233 Some(UserBackendConnection::YubiHsm2 {
3234 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3235 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3236 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3237 },
3238 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3239 connections: BTreeSet::from_iter([
3240 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3241 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3242 ]),
3243 mapping: YubiHsm2UserMapping::Signing {
3244 authentication_key_id: "5".parse()?,
3245 signing_key_id: "1".parse()?,
3246 key_setup: SigningKeySetup::new(
3247 KeyType::Curve25519,
3248 vec![KeyMechanism::EdDsaSignature],
3249 None,
3250 SignatureType::EdDsa,
3251 CryptographicKeyContext::OpenPgp {
3252 user_ids: OpenPgpUserIdList::new(vec![
3253 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3254 ])?,
3255 version: "v4".parse()?,
3256 notations: Default::default(),
3257 },
3258 )?,
3259 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3260 system_user: "yubihsm2-signing".parse()?,
3261 domain: Domain::One,
3262 }
3263 })
3264 )]
3265 #[case::none("foo", None)]
3266 fn config_user_backend_connection(
3267 default_config: TestResult<Config>,
3268 #[case] system_user: &str,
3269 #[case] expected_connection: Option<UserBackendConnection>,
3270 ) -> TestResult {
3271 let config = default_config?;
3272 assert_eq!(
3273 expected_connection,
3274 config.user_backend_connection(&system_user.parse()?)
3275 );
3276
3277 Ok(())
3278 }
3279
3280 #[rstest]
3283 #[case::no_filter(
3284 &[],
3285 vec![
3286 UserBackendConnection::NetHsm {
3287 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3288 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3289 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3290 },
3291 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3292 connections: BTreeSet::from_iter([
3293 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3294 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3295 ]),
3296 mapping: NetHsmUserMapping::Admin("admin".parse()?)
3297 },
3298 UserBackendConnection::NetHsm {
3299 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3300 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3301 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3302 },
3303 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3304 connections: BTreeSet::from_iter([
3305 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3306 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3307 ]),
3308 mapping: NetHsmUserMapping::Backup{
3309 backend_user: "backup".parse()?,
3310 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3311 system_user: "nethsm-backup".parse()?,
3312 }
3313 },
3314 UserBackendConnection::NetHsm {
3315 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3316 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3317 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3318 },
3319 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3320 connections: BTreeSet::from_iter([
3321 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3322 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3323 ]),
3324 mapping: NetHsmUserMapping::HermeticMetrics {
3325 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3326 system_user: "nethsm-hermetic-metrics".parse()?,
3327 }
3328 },
3329 UserBackendConnection::NetHsm {
3330 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3331 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3332 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3333 },
3334 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3335 connections: BTreeSet::from_iter([
3336 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3337 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3338 ]),
3339 mapping: NetHsmUserMapping::Metrics {
3340 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3341 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3342 system_user: "nethsm-metrics".parse()?,
3343 }
3344 },
3345 UserBackendConnection::NetHsm {
3346 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3347 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3348 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3349 },
3350 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3351 connections: BTreeSet::from_iter([
3352 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3353 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3354 ]),
3355 mapping: NetHsmUserMapping::Signing {
3356 backend_user: "signing".parse()?,
3357 signing_key_id: "signing1".parse()?,
3358 key_setup: SigningKeySetup::new(
3359 KeyType::Curve25519,
3360 vec![KeyMechanism::EdDsaSignature],
3361 None,
3362 SignatureType::EdDsa,
3363 CryptographicKeyContext::OpenPgp {
3364 user_ids: OpenPgpUserIdList::new(vec![
3365 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3366 ])?,
3367 version: "v4".parse()?,
3368 notations: Default::default(),
3369 },
3370 )?,
3371 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3372 system_user: "nethsm-signing".parse()?,
3373 tag: "signing1".to_string(),
3374 }
3375 },
3376 UserBackendConnection::YubiHsm2 {
3377 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3378 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3379 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3380 },
3381 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3382 connections: BTreeSet::from_iter([
3383 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3384 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3385 ]),
3386 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3387 },
3388 UserBackendConnection::YubiHsm2 {
3389 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3390 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3391 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3392 },
3393 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3394 connections: BTreeSet::from_iter([
3395 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3396 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3397 ]),
3398 mapping: YubiHsm2UserMapping::AuditLog {
3399 authentication_key_id: "3".parse()?,
3400 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3401 system_user: "yubihsm2-audit-log".parse()?,
3402 },
3403 },
3404 UserBackendConnection::YubiHsm2 {
3405 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3406 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3407 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3408 },
3409 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3410 connections: BTreeSet::from_iter([
3411 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3412 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3413 ]),
3414 mapping: YubiHsm2UserMapping::Backup{
3415 authentication_key_id: "2".parse()?,
3416 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3417 system_user: "yubihsm2-backup".parse()?,
3418 },
3419 },
3420 UserBackendConnection::YubiHsm2 {
3421 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3422 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3423 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3424 },
3425 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3426 connections: BTreeSet::from_iter([
3427 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3428 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3429 ]),
3430 mapping: YubiHsm2UserMapping::HermeticAuditLog {
3431 authentication_key_id: "4".parse()?,
3432 system_user: "yubihsm2-hermetic-audit-log".parse()?,
3433 },
3434 },
3435 UserBackendConnection::YubiHsm2 {
3436 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3437 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3438 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3439 },
3440 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3441 connections: BTreeSet::from_iter([
3442 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3443 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3444 ]),
3445 mapping: YubiHsm2UserMapping::Signing {
3446 authentication_key_id: "5".parse()?,
3447 signing_key_id: "1".parse()?,
3448 key_setup: SigningKeySetup::new(
3449 KeyType::Curve25519,
3450 vec![KeyMechanism::EdDsaSignature],
3451 None,
3452 SignatureType::EdDsa,
3453 CryptographicKeyContext::OpenPgp {
3454 user_ids: OpenPgpUserIdList::new(vec![
3455 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3456 ])?,
3457 version: "v4".parse()?,
3458 notations: Default::default(),
3459 },
3460 )?,
3461 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3462 system_user: "yubihsm2-signing".parse()?,
3463 domain: Domain::One,
3464 }
3465 },
3466 ],
3467 )]
3468 #[case::filter_admin(
3469 &[UserBackendConnectionFilter::Admin],
3470 vec![
3471 UserBackendConnection::NetHsm {
3472 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3473 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3474 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3475 },
3476 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3477 connections: BTreeSet::from_iter([
3478 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3479 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3480 ]),
3481 mapping: NetHsmUserMapping::Admin("admin".parse()?)
3482 },
3483 UserBackendConnection::YubiHsm2 {
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 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3491 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3492 ]),
3493 mapping: YubiHsm2UserMapping::Admin { authentication_key_id: "1".parse()? },
3494 },
3495 ],
3496 )]
3497 #[case::filter_non_admin(
3498 &[UserBackendConnectionFilter::NonAdmin],
3499 vec![
3500 UserBackendConnection::NetHsm {
3501 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3502 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3503 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3504 },
3505 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3506 connections: BTreeSet::from_iter([
3507 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3508 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3509 ]),
3510 mapping: NetHsmUserMapping::Backup{
3511 backend_user: "backup".parse()?,
3512 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3513 system_user: "nethsm-backup".parse()?,
3514 }
3515 },
3516 UserBackendConnection::NetHsm {
3517 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3518 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3519 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3520 },
3521 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3522 connections: BTreeSet::from_iter([
3523 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3524 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3525 ]),
3526 mapping: NetHsmUserMapping::HermeticMetrics {
3527 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
3528 system_user: "nethsm-hermetic-metrics".parse()?,
3529 }
3530 },
3531 UserBackendConnection::NetHsm {
3532 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3533 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3534 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3535 },
3536 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3537 connections: BTreeSet::from_iter([
3538 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3539 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3540 ]),
3541 mapping: NetHsmUserMapping::Metrics {
3542 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
3543 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3544 system_user: "nethsm-metrics".parse()?,
3545 }
3546 },
3547 UserBackendConnection::NetHsm {
3548 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3549 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3550 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3551 },
3552 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3553 connections: BTreeSet::from_iter([
3554 Connection::new("https://nethsm1.example.org/".parse()?, ConnectionSecurity::Unsafe),
3555 Connection::new("https://nethsm2.example.org/".parse()?, ConnectionSecurity::Unsafe),
3556 ]),
3557 mapping: NetHsmUserMapping::Signing {
3558 backend_user: "signing".parse()?,
3559 signing_key_id: "signing1".parse()?,
3560 key_setup: SigningKeySetup::new(
3561 KeyType::Curve25519,
3562 vec![KeyMechanism::EdDsaSignature],
3563 None,
3564 SignatureType::EdDsa,
3565 CryptographicKeyContext::OpenPgp {
3566 user_ids: OpenPgpUserIdList::new(vec![
3567 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3568 ])?,
3569 version: "v4".parse()?,
3570 notations: Default::default(),
3571 },
3572 )?,
3573 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3574 system_user: "nethsm-signing".parse()?,
3575 tag: "signing1".to_string(),
3576 }
3577 },
3578 UserBackendConnection::YubiHsm2 {
3579 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3580 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3581 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3582 },
3583 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3584 connections: BTreeSet::from_iter([
3585 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3586 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3587 ]),
3588 mapping: YubiHsm2UserMapping::AuditLog {
3589 authentication_key_id: "3".parse()?,
3590 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3591 system_user: "yubihsm2-audit-log".parse()?,
3592 },
3593 },
3594 UserBackendConnection::YubiHsm2 {
3595 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3596 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3597 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3598 },
3599 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3600 connections: BTreeSet::from_iter([
3601 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3602 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3603 ]),
3604 mapping: YubiHsm2UserMapping::Backup{
3605 authentication_key_id: "2".parse()?,
3606 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3607 system_user: "yubihsm2-backup".parse()?,
3608 },
3609 },
3610 UserBackendConnection::YubiHsm2 {
3611 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3612 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3613 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3614 },
3615 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3616 connections: BTreeSet::from_iter([
3617 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3618 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3619 ]),
3620 mapping: YubiHsm2UserMapping::HermeticAuditLog {
3621 authentication_key_id: "4".parse()?,
3622 system_user: "yubihsm2-hermetic-audit-log".parse()?,
3623 },
3624 },
3625 UserBackendConnection::YubiHsm2 {
3626 admin_secret_handling: AdministrativeSecretHandling::ShamirsSecretSharing {
3627 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3628 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3629 },
3630 non_admin_secret_handling: NonAdministrativeSecretHandling::SystemdCreds,
3631 connections: BTreeSet::from_iter([
3632 YubiHsm2Connection::Usb {serial_number: "0012345678".parse()? },
3633 YubiHsm2Connection::Usb {serial_number: "0087654321".parse()? },
3634 ]),
3635 mapping: YubiHsm2UserMapping::Signing {
3636 authentication_key_id: "5".parse()?,
3637 signing_key_id: "1".parse()?,
3638 key_setup: SigningKeySetup::new(
3639 KeyType::Curve25519,
3640 vec![KeyMechanism::EdDsaSignature],
3641 None,
3642 SignatureType::EdDsa,
3643 CryptographicKeyContext::OpenPgp {
3644 user_ids: OpenPgpUserIdList::new(vec![
3645 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3646 ])?,
3647 version: "v4".parse()?,
3648 notations: Default::default(),
3649 },
3650 )?,
3651 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3652 system_user: "yubihsm2-signing".parse()?,
3653 domain: Domain::One,
3654 }
3655 },
3656 ],
3657 )]
3658 fn config_user_backend_connections(
3659 default_config: TestResult<Config>,
3660 #[case] filters: &[UserBackendConnectionFilter],
3661 #[case] expected_connections: Vec<UserBackendConnection>,
3662 ) -> TestResult {
3663 setup_terminal_logging(LevelFilter::Debug)?;
3664 let config = default_config?;
3665
3666 assert_eq!(
3667 expected_connections,
3668 config.user_backend_connections(filters)
3669 );
3670
3671 Ok(())
3672 }
3673
3674 #[rstest]
3679 fn config_to_yaml_string(
3680 default_system_config: TestResult<SystemConfig>,
3681 default_nethsm_config: TestResult<NetHsmConfig>,
3682 default_yubihsm2_config: TestResult<YubiHsm2Config>,
3683 ) -> TestResult {
3684 let config = ConfigBuilder::new(default_system_config?)
3685 .set_nethsm_config(default_nethsm_config?)
3686 .set_yubihsm2_config(default_yubihsm2_config?)
3687 .finish()?;
3688 let config_str = config.to_yaml_string()?;
3689
3690 with_settings!({
3691 description => "Configuration with system-wide, NetHSM and YubiHSM2 configuration",
3692 snapshot_path => SNAPSHOT_PATH,
3693 prepend_module_to_snapshot => false,
3694 }, {
3695 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), config_str);
3696 });
3697
3698 Ok(())
3699 }
3700
3701 #[rstest]
3704 fn config_authorized_key_entries(default_config: TestResult<Config>) -> TestResult {
3705 let config = default_config?;
3706 let expected: HashSet<AuthorizedKeyEntry> = HashSet::from_iter([
3707 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3708 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3709 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILWqWyMCk5BdSl1c3KYoLEokKr7qNVPbI1IbBhgEBQj5 user@host".parse()?,
3710 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
3711 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHxR0Oc+SWXkEvvZPitc6NvjvykgiKc9iauRI7tLYvcp user@host".parse()?,
3712 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETxhCqeZhfzFLfH0KFyw3u/w/dkRBUrft8tQm7DEVzY user@host".parse()?,
3713 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIClIXZdx0aDOPcIQA+6Qx68cwSUgGTL3TWzDSX3qUEOQ user@host".parse()?,
3714 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
3715 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOOCMo+ODRchqIiXm89TxF7avi+LXRtqWZdBAvJ1SG5g user@host".parse()?,
3716 "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3717 ]);
3718
3719 assert_eq!(
3720 config.authorized_key_entries(),
3721 expected.iter().collect::<HashSet<_>>()
3722 );
3723 Ok(())
3724 }
3725
3726 #[rstest]
3728 fn config_system_user_data(
3729 default_config: TestResult<Config>,
3730 raw_user_data: TestResult<Vec<(SystemUserId, Option<AuthorizedKeyEntry>)>>,
3731 ) -> TestResult {
3732 let config = default_config?;
3733 let raw_user_data = raw_user_data?;
3734 let expected: HashSet<SystemUserData> = HashSet::from_iter([
3735 SystemUserData::HostShareholder {
3736 system_user: &raw_user_data[0].0,
3737 ssh_authorized_key: raw_user_data[0]
3738 .1
3739 .as_ref()
3740 .expect("to have SSH authorized key"),
3741 },
3742 SystemUserData::HostShareholder {
3743 system_user: &raw_user_data[1].0,
3744 ssh_authorized_key: raw_user_data[1]
3745 .1
3746 .as_ref()
3747 .expect("to have SSH authorized key"),
3748 },
3749 SystemUserData::HostShareholder {
3750 system_user: &raw_user_data[2].0,
3751 ssh_authorized_key: raw_user_data[2]
3752 .1
3753 .as_ref()
3754 .expect("to have SSH authorized key"),
3755 },
3756 SystemUserData::HostDownloadNetworkConfig {
3757 system_user: &raw_user_data[3].0,
3758 ssh_authorized_key: raw_user_data[3]
3759 .1
3760 .as_ref()
3761 .expect("to have SSH authorized key"),
3762 },
3763 SystemUserData::BackendAdmin {
3764 system_user: raw_user_data[4].0.clone(),
3765 },
3766 SystemUserData::BackendBackup {
3767 system_user: &raw_user_data[5].0,
3768 ssh_authorized_key: raw_user_data[5]
3769 .1
3770 .as_ref()
3771 .expect("to have SSH authorized key"),
3772 },
3773 SystemUserData::BackendHermeticMetrics {
3774 system_user: &raw_user_data[6].0,
3775 },
3776 SystemUserData::BackendMetrics {
3777 system_user: &raw_user_data[7].0,
3778 ssh_authorized_key: raw_user_data[7]
3779 .1
3780 .as_ref()
3781 .expect("to have SSH authorized key"),
3782 },
3783 SystemUserData::BackendSign {
3784 system_user: &raw_user_data[8].0,
3785 ssh_authorized_key: raw_user_data[8]
3786 .1
3787 .as_ref()
3788 .expect("to have SSH authorized key"),
3789 },
3790 SystemUserData::BackendMetrics {
3791 system_user: &raw_user_data[10].0,
3792 ssh_authorized_key: raw_user_data[10]
3793 .1
3794 .as_ref()
3795 .expect("to have SSH authorized key"),
3796 },
3797 SystemUserData::BackendBackup {
3798 system_user: &raw_user_data[11].0,
3799 ssh_authorized_key: raw_user_data[11]
3800 .1
3801 .as_ref()
3802 .expect("to have SSH authorized key"),
3803 },
3804 SystemUserData::BackendHermeticMetrics {
3805 system_user: &raw_user_data[12].0,
3806 },
3807 SystemUserData::BackendSign {
3808 system_user: &raw_user_data[13].0,
3809 ssh_authorized_key: raw_user_data[13]
3810 .1
3811 .as_ref()
3812 .expect("to have SSH authorized key"),
3813 },
3814 ]);
3815
3816 assert_eq!(config.system_user_data(), expected);
3817 Ok(())
3818 }
3819
3820 #[rstest]
3822 fn config_system_user_ids(default_config: TestResult<Config>) -> TestResult {
3823 let config = default_config?;
3824 let expected: HashSet<SystemUserId> = HashSet::from_iter([
3825 "signstar-share-holder1".parse()?,
3826 "signstar-share-holder2".parse()?,
3827 "signstar-share-holder3".parse()?,
3828 "signstar-wireguard-download".parse()?,
3829 "nethsm-backup".parse()?,
3830 "nethsm-hermetic-metrics".parse()?,
3831 "nethsm-metrics".parse()?,
3832 "nethsm-signing".parse()?,
3833 "yubihsm2-audit-log".parse()?,
3834 "yubihsm2-backup".parse()?,
3835 "yubihsm2-hermetic-audit-log".parse()?,
3836 "yubihsm2-signing".parse()?,
3837 ]);
3838
3839 assert_eq!(
3840 config.system_user_ids(),
3841 expected.iter().collect::<HashSet<_>>()
3842 );
3843 Ok(())
3844 }
3845
3846 #[rstest]
3848 fn config_builder_new(
3849 default_system_config: TestResult<SystemConfig>,
3850 default_nethsm_config: TestResult<NetHsmConfig>,
3851 default_yubihsm2_config: TestResult<YubiHsm2Config>,
3852 ) -> TestResult {
3853 let _config = ConfigBuilder::new(default_system_config?)
3854 .set_nethsm_config(default_nethsm_config?)
3855 .set_yubihsm2_config(default_yubihsm2_config?)
3856 .finish()?;
3857
3858 Ok(())
3859 }
3860
3861 #[rstest]
3867 fn roundtrip_yaml_config(
3868 #[files("../fixtures/config/all_backends/*.yaml")] path: PathBuf,
3869 ) -> TestResult {
3870 let config_string = read_to_string(&path)?;
3871 let config = Config::from_file_path(&path)?;
3872
3873 assert_eq!(config.to_yaml_string()?, config_string);
3874
3875 Ok(())
3876 }
3877
3878 #[rstest]
3882 fn user_backend_connection_secret_handling(
3883 default_config: TestResult<Config>,
3884 ) -> TestResult {
3885 let config = default_config?;
3886 let admin_secret_handling = AdministrativeSecretHandling::ShamirsSecretSharing {
3887 number_of_shares: NonZeroUsize::new(3).expect("3 is larger than 0"),
3888 threshold: NonZeroUsize::new(2).expect("2 is larger than 0"),
3889 };
3890 let non_admin_secret_handling = NonAdministrativeSecretHandling::SystemdCreds;
3891
3892 for user in ["nethsm-signing", "yubihsm2-signing"] {
3893 let user_backend_connection = config
3894 .user_backend_connection(&user.parse()?)
3895 .expect("there to be a mapping of the requested name");
3896
3897 assert_eq!(
3898 user_backend_connection.admin_secret_handling(),
3899 admin_secret_handling
3900 );
3901 assert_eq!(
3902 user_backend_connection.non_admin_secret_handling(),
3903 non_admin_secret_handling
3904 );
3905 }
3906
3907 Ok(())
3908 }
3909 }
3910}