1use std::{
4 collections::{BTreeSet, HashSet},
5 fmt::Display,
6};
7
8use garde::Validate;
9use nethsm::{
10 Connection,
11 FullCredentials,
12 KeyId,
13 NamespaceId,
14 Passphrase,
15 SystemWideUserId,
16 UserId,
17 UserRole,
18};
19use serde::{Deserialize, Serialize};
20#[cfg(doc)]
21use signstar_crypto::key::{CryptographicKeyContext, KeyMechanism, KeyType};
22use signstar_crypto::{key::SigningKeySetup, traits::UserWithPassphrase};
23
24use crate::{
25 config::{
26 AuthorizedKeyEntry,
27 BackendDomainFilter,
28 BackendKeyIdFilter,
29 BackendUserIdFilter,
30 BackendUserIdKind,
31 ConfigAuthorizedKeyEntries,
32 ConfigSystemUserIds,
33 KeyCertificateState,
34 MappingAuthorizedKeyEntry,
35 MappingBackendDomain,
36 MappingBackendKeyId,
37 MappingBackendUserIds,
38 MappingBackendUserSecrets,
39 MappingSystemUserId,
40 SystemUserData,
41 SystemUserId,
42 duplicate_authorized_keys,
43 duplicate_backend_user_ids,
44 duplicate_domains,
45 duplicate_key_ids,
46 duplicate_system_user_ids,
47 },
48 nethsm::{KeyState, NetHsmBackendState, UserState},
49 state::{StateOrigin, StateOriginInfo},
50};
51
52#[derive(Debug, thiserror::Error)]
54pub enum Error {
55 #[error("The NetHSM user {metrics_user} is both in the Metrics and Operator role!")]
58 MetricsAlsoOperator {
59 metrics_user: SystemWideUserId,
63 },
64
65 #[error("The NetHSM user {user} cannot be found")]
67 UserIdNotFound {
68 user: String,
70 },
71}
72
73#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
75pub enum FilterUserKeys {
76 All,
78
79 Namespaced,
81
82 Namespace(NamespaceId),
84
85 SystemWide,
87
88 Tag(String),
90}
91
92#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
98pub struct NetHsmMetricsUsers {
99 metrics_user: SystemWideUserId,
100 operator_users: Vec<UserId>,
101}
102
103impl NetHsmMetricsUsers {
104 pub fn new(
134 metrics_user: SystemWideUserId,
135 operator_users: Vec<UserId>,
136 ) -> Result<Self, crate::Error> {
137 if operator_users.contains(metrics_user.as_ref()) {
139 return Err(Error::MetricsAlsoOperator { metrics_user }.into());
140 }
141
142 Ok(Self {
143 metrics_user,
144 operator_users,
145 })
146 }
147
148 pub fn get_users(&self) -> Vec<UserId> {
174 [
175 vec![self.metrics_user.clone().into()],
176 self.operator_users.clone(),
177 ]
178 .concat()
179 }
180
181 pub fn get_users_and_roles(&self) -> Vec<(UserId, UserRole)> {
207 [
208 vec![(self.metrics_user.clone().into(), UserRole::Metrics)],
209 self.operator_users
210 .iter()
211 .map(|user| (user.clone(), UserRole::Operator))
212 .collect(),
213 ]
214 .concat()
215 }
216}
217
218#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
220pub struct NetHsmConfigUserData<'a> {
221 pub user: &'a UserId,
223
224 pub role: UserRole,
226
227 pub tag: Option<&'a str>,
229}
230
231impl<'a> Display for NetHsmConfigUserData<'a> {
232 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233 write!(f, "{} (role: {}", self.user, self.role)?;
234 if let Some(tag) = self.tag {
235 write!(f, "; tag: {tag}")?;
236 }
237 write!(f, ")")?;
238
239 Ok(())
240 }
241}
242
243impl<'a> PartialEq<UserState> for NetHsmConfigUserData<'a> {
244 fn eq(&self, other: &UserState) -> bool {
252 self.user == &other.name && self.role == other.role && self.tag == other.tag.as_deref()
253 }
254}
255
256#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
258pub enum NetHsmUserKeysFilter {
259 All,
261
262 Namespaced,
264
265 SystemWide,
267}
268
269#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
271pub struct NetHsmConfigUserKeyData<'a> {
272 pub user: &'a UserId,
274
275 pub key_id: &'a KeyId,
277
278 pub key_setup: &'a SigningKeySetup,
280
281 pub tag: &'a str,
283}
284
285impl<'a> Display for NetHsmConfigUserKeyData<'a> {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 write!(f, "{} (", self.user)?;
288 if let Some(namespace) = self.user.namespace() {
289 write!(f, "namespace: {namespace}; ")?;
290 }
291 write!(f, "tag: {}; ", self.tag)?;
292 write!(f, "type: {}; ", self.key_setup.key_type())?;
293 write!(
294 f,
295 "mechanisms: {}; ",
296 self.key_setup
297 .key_mechanisms()
298 .iter()
299 .map(|mechanism| mechanism.to_string())
300 .collect::<Vec<String>>()
301 .join(", ")
302 )?;
303 write!(f, "context: {}", self.key_setup.key_context())?;
304 write!(f, ")")?;
305
306 Ok(())
307 }
308}
309
310impl<'a> PartialEq<KeyState> for NetHsmConfigUserKeyData<'a> {
311 fn eq(&self, other: &KeyState) -> bool {
326 self.user.namespace() == other.namespace.as_ref()
327 && self.key_id == &other.name
328 && self.key_setup.key_type() == other.key_type
329 && self.key_setup.key_mechanisms() == other.mechanisms
330 && self.tag == other.tag
331 && if let KeyCertificateState::KeyContext(context) = &other.key_cert_state {
332 self.key_setup.key_context() == context
333 } else {
334 false
335 }
336 }
337}
338
339#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
341#[serde(rename_all = "snake_case")]
342pub enum NetHsmUserMapping {
343 Admin(UserId),
345
346 Backup {
348 backend_user: SystemWideUserId,
350 ssh_authorized_key: AuthorizedKeyEntry,
352 system_user: SystemUserId,
354 },
355
356 HermeticMetrics {
360 backend_users: NetHsmMetricsUsers,
363 system_user: SystemUserId,
365 },
366
367 Metrics {
371 backend_users: NetHsmMetricsUsers,
374 ssh_authorized_key: AuthorizedKeyEntry,
376 system_user: SystemUserId,
378 },
379
380 Signing {
385 backend_user: UserId,
387 signing_key_id: KeyId,
389 key_setup: SigningKeySetup,
391 ssh_authorized_key: AuthorizedKeyEntry,
393 system_user: SystemUserId,
395 tag: String,
397 },
398}
399
400impl NetHsmUserMapping {
401 pub fn namespaces(&self) -> Vec<&NamespaceId> {
403 match self {
404 Self::Admin(backend_user) | Self::Signing { backend_user, .. } => {
405 if let Some(namespace) = backend_user.namespace() {
406 vec![namespace]
407 } else {
408 Vec::new()
409 }
410 }
411 Self::Backup { .. } => Vec::new(),
412 Self::HermeticMetrics { backend_users, .. } | Self::Metrics { backend_users, .. } => {
413 backend_users
414 .operator_users
415 .iter()
416 .filter_map(|user_id| user_id.namespace())
417 .collect::<Vec<_>>()
418 }
419 }
420 }
421
422 pub fn tag(&self, namespace: Option<&NamespaceId>) -> Option<&str> {
428 match self {
429 Self::Signing {
430 backend_user, tag, ..
431 } => {
432 if namespace == backend_user.namespace() {
433 Some(tag.as_str())
434 } else {
435 None
436 }
437 }
438 Self::Admin(_)
439 | Self::Backup { .. }
440 | Self::HermeticMetrics { .. }
441 | Self::Metrics { .. } => None,
442 }
443 }
444
445 pub fn nethsm_user_ids(&self) -> Vec<UserId> {
447 match self {
448 Self::Admin(user_id) => vec![user_id.clone()],
449 Self::Backup { backend_user, .. } => vec![backend_user.as_ref().clone()],
450 Self::Metrics { backend_users, .. } | Self::HermeticMetrics { backend_users, .. } => {
451 backend_users.get_users()
452 }
453 Self::Signing { backend_user, .. } => vec![backend_user.clone()],
454 }
455 }
456
457 pub fn nethsm_config_user_data<'a>(&'a self) -> HashSet<NetHsmConfigUserData<'a>> {
460 match self {
461 Self::Admin(user_id) => HashSet::from_iter([NetHsmConfigUserData {
462 user: user_id,
463 role: UserRole::Administrator,
464 tag: None,
465 }]),
466 Self::Backup { backend_user, .. } => HashSet::from_iter([NetHsmConfigUserData {
467 user: backend_user.as_ref(),
468 role: UserRole::Backup,
469 tag: None,
470 }]),
471 Self::Metrics { backend_users, .. } | Self::HermeticMetrics { backend_users, .. } => {
472 let mut users = backend_users
473 .operator_users
474 .iter()
475 .map(|user_id| NetHsmConfigUserData {
476 user: user_id,
477 role: UserRole::Operator,
478 tag: None,
479 })
480 .collect::<Vec<_>>();
481 users.push(NetHsmConfigUserData {
482 user: backend_users.metrics_user.as_ref(),
483 role: UserRole::Metrics,
484 tag: None,
485 });
486 HashSet::from_iter(users)
487 }
488 Self::Signing {
489 backend_user, tag, ..
490 } => HashSet::from_iter([NetHsmConfigUserData {
491 user: backend_user,
492 role: UserRole::Operator,
493 tag: Some(tag.as_ref()),
494 }]),
495 }
496 }
497
498 pub fn nethsm_config_user_key_data<'a>(
504 &'a self,
505 filter: NetHsmUserKeysFilter,
506 ) -> Option<NetHsmConfigUserKeyData<'a>> {
507 match self {
508 Self::Admin(_)
509 | Self::Backup { .. }
510 | Self::Metrics { .. }
511 | Self::HermeticMetrics { .. } => None,
512 Self::Signing {
513 backend_user,
514 signing_key_id,
515 key_setup,
516 tag,
517 ..
518 } => {
519 if matches!(filter, NetHsmUserKeysFilter::All)
520 || (matches!(filter, NetHsmUserKeysFilter::Namespaced)
521 && backend_user.is_namespaced())
522 || (matches!(filter, NetHsmUserKeysFilter::SystemWide)
523 && !backend_user.is_namespaced())
524 {
525 Some(NetHsmConfigUserKeyData {
526 user: backend_user,
527 key_id: signing_key_id,
528 key_setup,
529 tag,
530 })
531 } else {
532 None
533 }
534 }
535 }
536 }
537}
538
539impl MappingSystemUserId for NetHsmUserMapping {
540 fn system_user_id(&self) -> Option<&SystemUserId> {
541 match self {
542 Self::Admin(_) => None,
543 Self::Backup { system_user, .. }
544 | Self::Metrics { system_user, .. }
545 | Self::HermeticMetrics { system_user, .. }
546 | Self::Signing { system_user, .. } => Some(system_user),
547 }
548 }
549}
550
551impl MappingAuthorizedKeyEntry for NetHsmUserMapping {
552 fn authorized_key_entry(&self) -> Option<&AuthorizedKeyEntry> {
553 match self {
554 Self::Admin(_) | Self::HermeticMetrics { .. } => None,
555 Self::Backup {
556 ssh_authorized_key, ..
557 }
558 | Self::Metrics {
559 ssh_authorized_key, ..
560 }
561 | Self::Signing {
562 ssh_authorized_key, ..
563 } => Some(ssh_authorized_key),
564 }
565 }
566}
567
568impl MappingBackendUserIds for NetHsmUserMapping {
569 fn backend_user_ids(&self, filter: BackendUserIdFilter) -> Vec<String> {
570 match self {
571 Self::Admin(user_id) => {
572 if [BackendUserIdKind::Admin, BackendUserIdKind::Any]
573 .contains(&filter.backend_user_id_kind)
574 {
575 Some(vec![user_id.to_string()])
576 } else {
577 None
578 }
579 }
580 Self::Backup { backend_user, .. } => {
581 if [
582 BackendUserIdKind::Any,
583 BackendUserIdKind::Backup,
584 BackendUserIdKind::NonAdmin,
585 ]
586 .contains(&filter.backend_user_id_kind)
587 {
588 Some(vec![backend_user.to_string()])
589 } else {
590 None
591 }
592 }
593 Self::Metrics { backend_users, .. } | Self::HermeticMetrics { backend_users, .. } => {
594 match filter.backend_user_id_kind {
595 BackendUserIdKind::Admin
596 | BackendUserIdKind::Backup
597 | BackendUserIdKind::Signing => None,
598 BackendUserIdKind::Metrics => {
599 Some(vec![backend_users.metrics_user.to_string()])
600 }
601 BackendUserIdKind::NonAdmin | BackendUserIdKind::Any => Some(
602 backend_users
603 .get_users()
604 .iter()
605 .map(ToString::to_string)
606 .collect(),
607 ),
608 BackendUserIdKind::Observer => Some(
609 backend_users
610 .operator_users
611 .iter()
612 .map(ToString::to_string)
613 .collect(),
614 ),
615 }
616 }
617 Self::Signing { backend_user, .. } => {
618 if [
619 BackendUserIdKind::Any,
620 BackendUserIdKind::NonAdmin,
621 BackendUserIdKind::Signing,
622 ]
623 .contains(&filter.backend_user_id_kind)
624 {
625 Some(vec![backend_user.to_string()])
626 } else {
627 None
628 }
629 }
630 }
631 .unwrap_or_default()
632 }
633
634 fn backend_user_with_passphrase(
635 &self,
636 name: &str,
637 passphrase: Passphrase,
638 ) -> Result<Box<dyn UserWithPassphrase>, crate::Error> {
639 for user in self.nethsm_user_ids() {
640 if user.to_string() == name {
641 return Ok(Box::new(FullCredentials::new(user, passphrase)));
642 }
643 }
644
645 Err(Error::UserIdNotFound {
646 user: name.to_string(),
647 }
648 .into())
649 }
650
651 fn backend_users_with_new_passphrase(
652 &self,
653 filter: BackendUserIdFilter,
654 ) -> Vec<Box<dyn UserWithPassphrase>> {
655 if let Some(backend_user_ids) = match self {
656 Self::Admin(user_id) => {
657 if [BackendUserIdKind::Any, BackendUserIdKind::Admin]
658 .contains(&filter.backend_user_id_kind)
659 {
660 Some(vec![user_id.clone()])
661 } else {
662 None
663 }
664 }
665 Self::Backup { backend_user, .. } => {
666 if [
667 BackendUserIdKind::Any,
668 BackendUserIdKind::Backup,
669 BackendUserIdKind::NonAdmin,
670 ]
671 .contains(&filter.backend_user_id_kind)
672 {
673 Some(vec![UserId::from(backend_user.clone())])
674 } else {
675 None
676 }
677 }
678 Self::Metrics { backend_users, .. } | Self::HermeticMetrics { backend_users, .. } => {
679 match filter.backend_user_id_kind {
680 BackendUserIdKind::Admin
681 | BackendUserIdKind::Backup
682 | BackendUserIdKind::Signing => None,
683 BackendUserIdKind::Metrics => {
684 Some(vec![backend_users.metrics_user.as_ref().clone()])
685 }
686 BackendUserIdKind::NonAdmin | BackendUserIdKind::Any => {
687 Some(backend_users.get_users().to_vec())
688 }
689 BackendUserIdKind::Observer => Some(backend_users.operator_users.to_vec()),
690 }
691 }
692 Self::Signing { backend_user, .. } => {
693 if [
694 BackendUserIdKind::Any,
695 BackendUserIdKind::NonAdmin,
696 BackendUserIdKind::Signing,
697 ]
698 .contains(&filter.backend_user_id_kind)
699 {
700 Some(vec![backend_user.clone()])
701 } else {
702 None
703 }
704 }
705 } {
706 backend_user_ids
707 .into_iter()
708 .map(|backend_user_id| {
709 Box::new(FullCredentials::new(
710 backend_user_id,
711 Passphrase::generate(None),
712 )) as Box<dyn UserWithPassphrase>
713 })
714 .collect()
715 } else {
716 Vec::new()
717 }
718 }
719}
720
721impl<'a> From<&'a NetHsmUserMapping> for SystemUserData<'a> {
722 fn from(value: &'a NetHsmUserMapping) -> Self {
723 match value {
724 NetHsmUserMapping::Admin(..) => Self::BackendAdmin {
725 system_user: SystemUserId::root(),
726 },
727 NetHsmUserMapping::Backup {
728 ssh_authorized_key,
729 system_user,
730 ..
731 } => Self::BackendBackup {
732 system_user,
733 ssh_authorized_key,
734 },
735 NetHsmUserMapping::HermeticMetrics { system_user, .. } => {
736 Self::BackendHermeticMetrics { system_user }
737 }
738 NetHsmUserMapping::Metrics {
739 ssh_authorized_key,
740 system_user,
741 ..
742 } => Self::BackendMetrics {
743 system_user,
744 ssh_authorized_key,
745 },
746 NetHsmUserMapping::Signing {
747 ssh_authorized_key,
748 system_user,
749 ..
750 } => Self::BackendSign {
751 system_user,
752 ssh_authorized_key,
753 },
754 }
755 }
756}
757
758#[derive(Clone, Debug)]
760pub struct NetHsmBackendKeyIdFilter<'a> {
761 pub namespace: Option<&'a NamespaceId>,
762}
763
764impl<'a> BackendKeyIdFilter for NetHsmBackendKeyIdFilter<'a> {}
765
766impl<'a> MappingBackendKeyId<NetHsmBackendKeyIdFilter<'a>> for NetHsmUserMapping {
767 fn backend_key_id(&self, filter: &NetHsmBackendKeyIdFilter<'a>) -> Option<String> {
768 match self {
769 Self::Admin(_)
770 | Self::Backup { .. }
771 | Self::HermeticMetrics { .. }
772 | Self::Metrics { .. } => None,
773 Self::Signing {
774 backend_user,
775 signing_key_id,
776 ..
777 } => {
778 if filter.namespace == backend_user.namespace() {
779 Some(signing_key_id.to_string())
780 } else {
781 None
782 }
783 }
784 }
785 }
786}
787
788impl MappingBackendUserSecrets for NetHsmUserMapping {}
789
790#[derive(Clone, Debug)]
792pub struct NetHsmConfigDomainFilter<'a> {
793 pub namespace: Option<&'a NamespaceId>,
796}
797
798impl<'a> BackendDomainFilter for NetHsmConfigDomainFilter<'a> {}
799
800impl<'a> MappingBackendDomain<NetHsmConfigDomainFilter<'a>> for NetHsmUserMapping {
801 fn backend_domain(&self, filter: Option<&NetHsmConfigDomainFilter>) -> Option<String> {
807 let filter = if let Some(filter) = filter {
808 filter.namespace
809 } else {
810 None
811 };
812
813 self.tag(filter).map(ToString::to_string)
814 }
815}
816
817fn validate_nethsm_config_connections(
834 value: &BTreeSet<Connection>,
835 _context: &(),
836) -> garde::Result {
837 if value.is_empty() {
838 return Err(garde::Error::new("contains no connections"));
839 }
840
841 let urls = value
842 .iter()
843 .map(|connection| connection.url())
844 .collect::<Vec<_>>();
845 let duplicates = {
846 let mut duplicates = HashSet::new();
847
848 for url in urls.iter() {
849 if urls.iter().filter(|list_url| url == *list_url).count() > 1 {
850 duplicates.insert(url);
851 }
852 }
853 let mut duplicates = Vec::from_iter(duplicates);
854 duplicates.sort();
855 duplicates
856 };
857
858 if !duplicates.is_empty() {
859 return Err(garde::Error::new(format!(
860 "contains the duplicate URL{} {}",
861 if duplicates.len() > 1 { "s" } else { "" },
862 duplicates
863 .iter()
864 .map(|url| format!("\"{url}\""))
865 .collect::<Vec<_>>()
866 .join(", ")
867 )));
868 }
869
870 Ok(())
871}
872
873fn validate_nethsm_config_mappings(
906 value: &BTreeSet<NetHsmUserMapping>,
907 _context: &(),
908) -> garde::Result {
909 if value.is_empty() {
910 return Err(garde::Error::new("contains no user mappings"));
911 }
912
913 let duplicate_system_user_ids = duplicate_system_user_ids(value);
915
916 let duplicate_authorized_keys = duplicate_authorized_keys(value);
918
919 let missing_system_wide_admin = {
921 let num_system_admins = value
922 .iter()
923 .filter_map(|mapping| {
924 if let NetHsmUserMapping::Admin(user_id) = mapping
925 && !user_id.is_namespaced()
926 {
927 Some(user_id)
928 } else {
929 None
930 }
931 })
932 .count();
933
934 if num_system_admins == 0 {
935 Some("no system-wide administrator user".to_string())
936 } else {
937 None
938 }
939 };
940
941 let duplicate_backend_user_ids = duplicate_backend_user_ids(value);
943
944 let duplicate_system_wide_key_ids = duplicate_key_ids(
946 value,
947 &NetHsmBackendKeyIdFilter { namespace: None },
948 Some(" system-wide".to_string()),
949 );
950
951 let duplicate_system_wide_tags =
953 duplicate_domains(value, None, Some(" system-wide".to_string()), Some("tag"));
954
955 let all_namespaces = {
957 let mut all_namespaces = Vec::from_iter(
958 value
959 .iter()
960 .flat_map(|mapping| mapping.namespaces())
961 .collect::<HashSet<_>>(),
962 );
963 all_namespaces.sort();
964 all_namespaces
965 };
966
967 let namespaces_without_admin = {
969 let mut all_namespaces: HashSet<&NamespaceId> = HashSet::from_iter(all_namespaces.clone());
970
971 for mapping in value.iter() {
972 if let NetHsmUserMapping::Admin(user_id) = mapping
973 && let Some(namespace) = user_id.namespace()
974 {
975 all_namespaces.remove(namespace);
976 }
977 }
978
979 if all_namespaces.is_empty() {
980 None
981 } else {
982 let mut namespaces_without_admin = all_namespaces
983 .iter()
984 .map(|namespace| format!("\"{namespace}\""))
985 .collect::<Vec<_>>();
986 namespaces_without_admin.sort();
987 Some(format!(
988 "the namespace{} {} without an administrator user",
989 if namespaces_without_admin.len() > 1 {
990 "s"
991 } else {
992 ""
993 },
994 namespaces_without_admin.join(", ")
995 ))
996 }
997 };
998
999 let duplicate_namespaced_key_ids = {
1001 let mut all_duplicates = Vec::new();
1002
1003 for namespace in all_namespaces.iter() {
1004 let mut duplicates = duplicate_key_ids(
1005 value,
1006 &NetHsmBackendKeyIdFilter {
1007 namespace: Some(namespace),
1008 },
1009 Some(format!(" \"{namespace}\" namespaced")),
1010 );
1011 if let Some(message) = duplicates.take() {
1012 all_duplicates.push(message)
1013 }
1014 }
1015
1016 if all_duplicates.is_empty() {
1017 None
1018 } else {
1019 Some(all_duplicates.join("\n"))
1020 }
1021 };
1022
1023 let duplicate_namespaced_tags = {
1025 let mut all_duplicates = Vec::new();
1026
1027 for namespace in all_namespaces.iter() {
1028 let mut duplicates = duplicate_domains(
1029 value,
1030 Some(&NetHsmConfigDomainFilter {
1031 namespace: Some(namespace),
1032 }),
1033 Some(format!(" \"{namespace}\" namespaced")),
1034 Some("tag"),
1035 );
1036 if let Some(message) = duplicates.take() {
1037 all_duplicates.push(message)
1038 }
1039 }
1040
1041 if all_duplicates.is_empty() {
1042 None
1043 } else {
1044 Some(all_duplicates.join("\n"))
1045 }
1046 };
1047
1048 let messages = [
1049 duplicate_system_user_ids,
1050 duplicate_authorized_keys,
1051 missing_system_wide_admin,
1052 duplicate_backend_user_ids,
1053 duplicate_system_wide_key_ids,
1054 duplicate_system_wide_tags,
1055 namespaces_without_admin,
1056 duplicate_namespaced_key_ids,
1057 duplicate_namespaced_tags,
1058 ];
1059 let error_messages = {
1060 let mut error_messages = Vec::new();
1061
1062 for message in messages.iter().flatten() {
1063 error_messages.push(message.as_str());
1064 }
1065
1066 error_messages
1067 };
1068
1069 match error_messages.len() {
1070 0 => Ok(()),
1071 1 => Err(garde::Error::new(format!(
1072 "contains {}",
1073 error_messages.join("\n")
1074 ))),
1075 _ => Err(garde::Error::new(format!(
1076 "contains multiple issues:\n⤷ {}",
1077 error_messages.join("\n⤷ ")
1078 ))),
1079 }
1080}
1081
1082#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
1087#[serde(rename_all = "snake_case")]
1088pub struct NetHsmConfig {
1089 #[garde(custom(validate_nethsm_config_connections))]
1090 connections: BTreeSet<Connection>,
1091
1092 #[garde(custom(validate_nethsm_config_mappings))]
1093 mappings: BTreeSet<NetHsmUserMapping>,
1094}
1095
1096impl NetHsmConfig {
1097 pub fn new(
1100 connections: BTreeSet<Connection>,
1101 mappings: BTreeSet<NetHsmUserMapping>,
1102 ) -> Result<Self, crate::Error> {
1103 let config = Self {
1104 connections,
1105 mappings,
1106 };
1107
1108 config
1109 .validate()
1110 .map_err(|source| crate::Error::Validation {
1111 context: "validating a NetHSM specific configuration item".to_string(),
1112 source,
1113 })?;
1114
1115 Ok(config)
1116 }
1117
1118 pub fn connections(&self) -> &BTreeSet<Connection> {
1120 &self.connections
1121 }
1122
1123 pub fn mappings(&self) -> &BTreeSet<NetHsmUserMapping> {
1125 &self.mappings
1126 }
1127}
1128
1129impl ConfigAuthorizedKeyEntries for NetHsmConfig {
1130 fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
1131 self.mappings
1132 .iter()
1133 .filter_map(|mapping| mapping.authorized_key_entry())
1134 .collect()
1135 }
1136}
1137
1138impl ConfigSystemUserIds for NetHsmConfig {
1139 fn system_user_ids(&self) -> HashSet<&SystemUserId> {
1140 self.mappings
1141 .iter()
1142 .filter_map(|mapping| mapping.system_user_id())
1143 .collect()
1144 }
1145}
1146
1147#[derive(Debug, Eq, PartialEq)]
1152pub struct NetHsmConfigState<'a> {
1153 pub(crate) user_data: Vec<NetHsmConfigUserData<'a>>,
1155 pub(crate) key_data: Vec<NetHsmConfigUserKeyData<'a>>,
1157}
1158
1159impl<'a> NetHsmConfigState<'a> {
1160 pub const STATE_NAME: &'static str = "NetHSM config";
1162}
1163
1164impl<'a> From<&'a NetHsmConfig> for NetHsmConfigState<'a> {
1165 fn from(value: &'a NetHsmConfig) -> Self {
1166 let (key_data, user_data) = {
1167 let mut key_data = Vec::new();
1168 let mut user_data = Vec::new();
1169
1170 for mapping in value.mappings() {
1171 if let Some(user_key_data) =
1172 mapping.nethsm_config_user_key_data(NetHsmUserKeysFilter::All)
1173 {
1174 key_data.push(user_key_data)
1175 }
1176 user_data.extend(mapping.nethsm_config_user_data());
1177 }
1178
1179 (key_data, user_data)
1180 };
1181
1182 Self {
1183 user_data,
1184 key_data,
1185 }
1186 }
1187}
1188
1189impl<'a> StateOriginInfo for NetHsmConfigState<'a> {
1190 fn state_name(&self) -> &str {
1191 Self::STATE_NAME
1192 }
1193
1194 fn state_origin(&self) -> StateOrigin {
1195 StateOrigin::Config
1196 }
1197}
1198
1199impl<'a> PartialEq<NetHsmBackendState> for NetHsmConfigState<'a> {
1200 fn eq(&self, other: &NetHsmBackendState) -> bool {
1201 if self.user_data.len() != other.user_states.len()
1202 || self.key_data.len() != other.key_states.len()
1203 {
1204 return false;
1205 }
1206
1207 {
1208 let mut remaining_other_user_states: HashSet<&UserState> =
1209 HashSet::from_iter(other.user_states.iter());
1210 'outer: for self_user_data in self.user_data.iter() {
1211 for other_user_state in remaining_other_user_states.iter() {
1212 if self_user_data == *other_user_state {
1213 remaining_other_user_states.remove(*other_user_state);
1214 continue 'outer;
1215 }
1216 }
1217 return false;
1218 }
1219 if !remaining_other_user_states.is_empty() {
1220 return false;
1221 }
1222 }
1223
1224 {
1225 let mut remaining_other_key_states: HashSet<&KeyState> =
1226 HashSet::from_iter(other.key_states.iter());
1227 'outer: for self_key_data in self.key_data.iter() {
1228 for other_user_state in remaining_other_key_states.iter() {
1229 if self_key_data == *other_user_state {
1230 remaining_other_key_states.remove(*other_user_state);
1231 continue 'outer;
1232 }
1233 }
1234 return false;
1235 }
1236 if !remaining_other_key_states.is_empty() {
1237 return false;
1238 }
1239 }
1240
1241 true
1242 }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247 use std::thread::current;
1248
1249 use insta::{assert_snapshot, with_settings};
1250 use log::debug;
1251 use rstest::{fixture, rstest};
1252 use signstar_crypto::{
1253 key::{CryptographicKeyContext, KeyMechanism, KeyType, SignatureType, SigningKeySetup},
1254 openpgp::OpenPgpUserIdList,
1255 };
1256 use testresult::TestResult;
1257
1258 use super::*;
1259
1260 const SNAPSHOT_PATH: &str = "fixtures/nethsm_config/";
1261
1262 #[test]
1263 fn nethsm_metrics_users_succeeds() -> TestResult {
1264 NetHsmMetricsUsers::new(
1265 SystemWideUserId::new("metrics".to_string())?,
1266 vec![
1267 UserId::new("operator".to_string())?,
1268 UserId::new("ns1~operator".to_string())?,
1269 ],
1270 )?;
1271 Ok(())
1272 }
1273
1274 #[test]
1275 fn nethsm_metrics_users_fails() -> TestResult {
1276 if let Ok(user) = NetHsmMetricsUsers::new(
1277 SystemWideUserId::new("metrics".to_string())?,
1278 vec![
1279 UserId::new("metrics".to_string())?,
1280 UserId::new("ns1~operator".to_string())?,
1281 ],
1282 ) {
1283 panic!("Succeeded creating a NetHsmMetricsUsers, but should have failed:\n{user:?}")
1284 }
1285 Ok(())
1286 }
1287
1288 #[rstest]
1289 #[case::admin(NetHsmUserMapping::Admin("admin".parse()?), vec!["admin".parse()?])]
1290 #[case::backup(
1291 NetHsmUserMapping::Backup{
1292 backend_user: "backup".parse()?,
1293 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1294 system_user: "backup-user".parse()?,
1295 },
1296 vec!["backup".parse()?],
1297 )]
1298 #[case::backup(
1299 NetHsmUserMapping::Metrics{
1300 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1301 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1302 system_user: "metrics-user".parse()?,
1303 },
1304 vec!["metrics".parse()?, "keymetrics".parse()?],
1305 )]
1306 #[case::backup(
1307 NetHsmUserMapping::Signing {
1308 backend_user: "signing".parse()?,
1309 signing_key_id: "signing1".parse()?,
1310 key_setup: SigningKeySetup::new(
1311 KeyType::Curve25519,
1312 vec![KeyMechanism::EdDsaSignature],
1313 None,
1314 SignatureType::EdDsa,
1315 CryptographicKeyContext::OpenPgp {
1316 user_ids: OpenPgpUserIdList::new(vec![
1317 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1318 ])?,
1319 version: "v4".parse()?,
1320 },
1321 )?,
1322 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1323 system_user: "signing-user".parse()?,
1324 tag: "signing1".to_string(),
1325 },
1326 vec!["signing".parse()?],
1327 )]
1328 fn nethsm_user_mapping_nethsm_user_ids(
1329 #[case] mapping: NetHsmUserMapping,
1330 #[case] expected: Vec<UserId>,
1331 ) -> TestResult {
1332 assert_eq!(mapping.nethsm_user_ids(), expected,);
1333
1334 Ok(())
1335 }
1336
1337 #[rstest]
1338 #[case::admin(
1339 NetHsmUserMapping::Admin("admin".parse()?),
1340 vec![("admin".parse()?, UserRole::Administrator, None)],
1341 )]
1342 #[case::backup(
1343 NetHsmUserMapping::Backup{
1344 backend_user: "backup".parse()?,
1345 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1346 system_user: "backup-user".parse()?,
1347 },
1348 vec![("backup".parse()?, UserRole::Backup, None)],
1349 )]
1350 #[case::metrics(
1351 NetHsmUserMapping::Metrics{
1352 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1353 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1354 system_user: "metrics-user".parse()?,
1355 },
1356 vec![
1357 ("keymetrics".parse()?, UserRole::Operator, None),
1358 ("metrics".parse()?, UserRole::Metrics, None),
1359 ],
1360 )]
1361 #[case::signing(
1362 NetHsmUserMapping::Signing {
1363 backend_user: "signing".parse()?,
1364 signing_key_id: "signing1".parse()?,
1365 key_setup: SigningKeySetup::new(
1366 KeyType::Curve25519,
1367 vec![KeyMechanism::EdDsaSignature],
1368 None,
1369 SignatureType::EdDsa,
1370 CryptographicKeyContext::OpenPgp {
1371 user_ids: OpenPgpUserIdList::new(vec![
1372 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1373 ])?,
1374 version: "v4".parse()?,
1375 },
1376 )?,
1377 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1378 system_user: "signing-user".parse()?,
1379 tag: "signing1".to_string(),
1380 },
1381 vec![("signing".parse()?, UserRole::Operator, Some("signing1"))],
1382 )]
1383 fn nethsm_user_mapping_nethsm_config_user_data(
1384 #[case] mapping: NetHsmUserMapping,
1385 #[case] expected: Vec<(UserId, UserRole, Option<&str>)>,
1386 ) -> TestResult {
1387 let expected = expected
1388 .iter()
1389 .map(|(user, role, tag)| NetHsmConfigUserData {
1390 user,
1391 role: *role,
1392 tag: *tag,
1393 })
1394 .collect::<HashSet<_>>();
1395 assert_eq!(mapping.nethsm_config_user_data(), expected);
1396
1397 Ok(())
1398 }
1399
1400 #[rstest]
1401 #[case::admin_filter_all(
1402 NetHsmUserMapping::Admin("admin".parse()?),
1403 NetHsmUserKeysFilter::All,
1404 None,
1405 )]
1406 #[case::admin_filter_namespace(
1407 NetHsmUserMapping::Admin("admin".parse()?),
1408 NetHsmUserKeysFilter::Namespaced,
1409 None,
1410 )]
1411 #[case::admin_filter_system_wide(
1412 NetHsmUserMapping::Admin("admin".parse()?),
1413 NetHsmUserKeysFilter::SystemWide,
1414 None,
1415 )]
1416 #[case::backup_filter_all(
1417 NetHsmUserMapping::Backup{
1418 backend_user: "backup".parse()?,
1419 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1420 system_user: "backup-user".parse()?,
1421 },
1422 NetHsmUserKeysFilter::All,
1423 None,
1424 )]
1425 #[case::backup_filter_namespaced(
1426 NetHsmUserMapping::Backup{
1427 backend_user: "backup".parse()?,
1428 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1429 system_user: "backup-user".parse()?,
1430 },
1431 NetHsmUserKeysFilter::Namespaced,
1432 None,
1433 )]
1434 #[case::backup_filter_system_wide(
1435 NetHsmUserMapping::Backup{
1436 backend_user: "backup".parse()?,
1437 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1438 system_user: "backup-user".parse()?,
1439 },
1440 NetHsmUserKeysFilter::SystemWide,
1441 None,
1442 )]
1443 #[case::metrics_filter_all(
1444 NetHsmUserMapping::Metrics{
1445 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1446 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1447 system_user: "metrics-user".parse()?,
1448 },
1449 NetHsmUserKeysFilter::All,
1450 None,
1451 )]
1452 #[case::metrics_filter_namespaced(
1453 NetHsmUserMapping::Metrics{
1454 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1455 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1456 system_user: "metrics-user".parse()?,
1457 },
1458 NetHsmUserKeysFilter::Namespaced,
1459 None,
1460 )]
1461 #[case::metrics_filter_system_wide(
1462 NetHsmUserMapping::Metrics{
1463 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1464 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1465 system_user: "metrics-user".parse()?,
1466 },
1467 NetHsmUserKeysFilter::SystemWide,
1468 None,
1469 )]
1470 #[case::signing_system_wide_filter_all(
1471 NetHsmUserMapping::Signing {
1472 backend_user: "signing".parse()?,
1473 signing_key_id: "signing1".parse()?,
1474 key_setup: SigningKeySetup::new(
1475 KeyType::Curve25519,
1476 vec![KeyMechanism::EdDsaSignature],
1477 None,
1478 SignatureType::EdDsa,
1479 CryptographicKeyContext::OpenPgp {
1480 user_ids: OpenPgpUserIdList::new(vec![
1481 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1482 ])?,
1483 version: "v4".parse()?,
1484 },
1485 )?,
1486 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1487 system_user: "signing-user".parse()?,
1488 tag: "signing1".to_string(),
1489 },
1490 NetHsmUserKeysFilter::All,
1491 Some((
1492 "signing".parse()?,
1493 "signing1".parse()?,
1494 SigningKeySetup::new(
1495 KeyType::Curve25519,
1496 vec![KeyMechanism::EdDsaSignature],
1497 None,
1498 SignatureType::EdDsa,
1499 CryptographicKeyContext::OpenPgp {
1500 user_ids: OpenPgpUserIdList::new(vec![
1501 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1502 ])?,
1503 version: "v4".parse()?,
1504 },
1505 )?,
1506 "signing1",
1507 )),
1508 )]
1509 #[case::signing_system_wide_filter_namespaced(
1510 NetHsmUserMapping::Signing {
1511 backend_user: "signing".parse()?,
1512 signing_key_id: "signing1".parse()?,
1513 key_setup: SigningKeySetup::new(
1514 KeyType::Curve25519,
1515 vec![KeyMechanism::EdDsaSignature],
1516 None,
1517 SignatureType::EdDsa,
1518 CryptographicKeyContext::OpenPgp {
1519 user_ids: OpenPgpUserIdList::new(vec![
1520 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1521 ])?,
1522 version: "v4".parse()?,
1523 },
1524 )?,
1525 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1526 system_user: "signing-user".parse()?,
1527 tag: "signing1".to_string(),
1528 },
1529 NetHsmUserKeysFilter::Namespaced,
1530 None,
1531 )]
1532 #[case::signing_system_wide_filter_system_wide(
1533 NetHsmUserMapping::Signing {
1534 backend_user: "signing".parse()?,
1535 signing_key_id: "signing1".parse()?,
1536 key_setup: SigningKeySetup::new(
1537 KeyType::Curve25519,
1538 vec![KeyMechanism::EdDsaSignature],
1539 None,
1540 SignatureType::EdDsa,
1541 CryptographicKeyContext::OpenPgp {
1542 user_ids: OpenPgpUserIdList::new(vec![
1543 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1544 ])?,
1545 version: "v4".parse()?,
1546 },
1547 )?,
1548 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1549 system_user: "signing-user".parse()?,
1550 tag: "signing1".to_string(),
1551 },
1552 NetHsmUserKeysFilter::SystemWide,
1553 Some((
1554 "signing".parse()?,
1555 "signing1".parse()?,
1556 SigningKeySetup::new(
1557 KeyType::Curve25519,
1558 vec![KeyMechanism::EdDsaSignature],
1559 None,
1560 SignatureType::EdDsa,
1561 CryptographicKeyContext::OpenPgp {
1562 user_ids: OpenPgpUserIdList::new(vec![
1563 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1564 ])?,
1565 version: "v4".parse()?,
1566 },
1567 )?,
1568 "signing1",
1569 )),
1570 )]
1571 #[case::signing_namespaced_filter_all(
1572 NetHsmUserMapping::Signing {
1573 backend_user: "ns1~signing".parse()?,
1574 signing_key_id: "signing1".parse()?,
1575 key_setup: SigningKeySetup::new(
1576 KeyType::Curve25519,
1577 vec![KeyMechanism::EdDsaSignature],
1578 None,
1579 SignatureType::EdDsa,
1580 CryptographicKeyContext::OpenPgp {
1581 user_ids: OpenPgpUserIdList::new(vec![
1582 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1583 ])?,
1584 version: "v4".parse()?,
1585 },
1586 )?,
1587 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1588 system_user: "signing-user".parse()?,
1589 tag: "signing1".to_string(),
1590 },
1591 NetHsmUserKeysFilter::All,
1592 Some((
1593 "ns1~signing".parse()?,
1594 "signing1".parse()?,
1595 SigningKeySetup::new(
1596 KeyType::Curve25519,
1597 vec![KeyMechanism::EdDsaSignature],
1598 None,
1599 SignatureType::EdDsa,
1600 CryptographicKeyContext::OpenPgp {
1601 user_ids: OpenPgpUserIdList::new(vec![
1602 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1603 ])?,
1604 version: "v4".parse()?,
1605 },
1606 )?,
1607 "signing1",
1608 )),
1609 )]
1610 #[case::signing_system_wide_filter_namespaced(
1611 NetHsmUserMapping::Signing {
1612 backend_user: "ns1~signing".parse()?,
1613 signing_key_id: "signing1".parse()?,
1614 key_setup: SigningKeySetup::new(
1615 KeyType::Curve25519,
1616 vec![KeyMechanism::EdDsaSignature],
1617 None,
1618 SignatureType::EdDsa,
1619 CryptographicKeyContext::OpenPgp {
1620 user_ids: OpenPgpUserIdList::new(vec![
1621 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1622 ])?,
1623 version: "v4".parse()?,
1624 },
1625 )?,
1626 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1627 system_user: "signing-user".parse()?,
1628 tag: "signing1".to_string(),
1629 },
1630 NetHsmUserKeysFilter::Namespaced,
1631 Some((
1632 "ns1~signing".parse()?,
1633 "signing1".parse()?,
1634 SigningKeySetup::new(
1635 KeyType::Curve25519,
1636 vec![KeyMechanism::EdDsaSignature],
1637 None,
1638 SignatureType::EdDsa,
1639 CryptographicKeyContext::OpenPgp {
1640 user_ids: OpenPgpUserIdList::new(vec![
1641 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1642 ])?,
1643 version: "v4".parse()?,
1644 },
1645 )?,
1646 "signing1",
1647 )),
1648 )]
1649 #[case::signing_system_wide_filter_system_wide(
1650 NetHsmUserMapping::Signing {
1651 backend_user: "ns1~signing".parse()?,
1652 signing_key_id: "signing1".parse()?,
1653 key_setup: SigningKeySetup::new(
1654 KeyType::Curve25519,
1655 vec![KeyMechanism::EdDsaSignature],
1656 None,
1657 SignatureType::EdDsa,
1658 CryptographicKeyContext::OpenPgp {
1659 user_ids: OpenPgpUserIdList::new(vec![
1660 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1661 ])?,
1662 version: "v4".parse()?,
1663 },
1664 )?,
1665 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1666 system_user: "signing-user".parse()?,
1667 tag: "signing1".to_string(),
1668 },
1669 NetHsmUserKeysFilter::SystemWide,
1670 None,
1671 )]
1672 fn nethsm_user_mapping_nethsm_config_user_key_data(
1673 #[case] mapping: NetHsmUserMapping,
1674 #[case] filter: NetHsmUserKeysFilter,
1675 #[case] expected: Option<(UserId, KeyId, SigningKeySetup, &str)>,
1676 ) -> TestResult {
1677 let expected =
1678 expected
1679 .as_ref()
1680 .map(|(user, key_id, key_setup, tag)| NetHsmConfigUserKeyData {
1681 user,
1682 key_id,
1683 key_setup,
1684 tag,
1685 });
1686 assert_eq!(mapping.nethsm_config_user_key_data(filter), expected);
1687
1688 Ok(())
1689 }
1690
1691 #[rstest]
1692 #[case::admin_filter_admin(
1693 NetHsmUserMapping::Admin("admin".parse()?),
1694 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1695 vec!["admin"]
1696 )]
1697 #[case::admin_filter_any(
1698 NetHsmUserMapping::Admin("admin".parse()?),
1699 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1700 vec!["admin"]
1701 )]
1702 #[case::backup_filter_any(
1703 NetHsmUserMapping::Backup{
1704 backend_user: "backup".parse()?,
1705 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1706 system_user: "backup-user".parse()?,
1707 },
1708 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1709 vec!["backup"]
1710 )]
1711 #[case::backup_filter_backup(
1712 NetHsmUserMapping::Backup{
1713 backend_user: "backup".parse()?,
1714 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1715 system_user: "backup-user".parse()?,
1716 },
1717 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1718 vec!["backup"]
1719 )]
1720 #[case::backup_filter_non_admin(
1721 NetHsmUserMapping::Backup{
1722 backend_user: "backup".parse()?,
1723 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1724 system_user: "backup-user".parse()?,
1725 },
1726 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1727 vec!["backup"]
1728 )]
1729 #[case::hermetic_metrics_filter_any(
1730 NetHsmUserMapping::HermeticMetrics {
1731 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1732 system_user: "metrics-user".parse()?,
1733 },
1734 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1735 vec!["metrics", "keymetrics"]
1736 )]
1737 #[case::hermetic_metrics_filter_metrics(
1738 NetHsmUserMapping::HermeticMetrics {
1739 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1740 system_user: "metrics-user".parse()?,
1741 },
1742 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1743 vec!["metrics"]
1744 )]
1745 #[case::hermetic_metrics_filter_non_admin(
1746 NetHsmUserMapping::HermeticMetrics {
1747 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1748 system_user: "metrics-user".parse()?,
1749 },
1750 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1751 vec!["metrics", "keymetrics"]
1752 )]
1753 #[case::hermetic_metrics_filter_observer(
1754 NetHsmUserMapping::HermeticMetrics {
1755 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1756 system_user: "metrics-user".parse()?,
1757 },
1758 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1759 vec!["keymetrics"]
1760 )]
1761 #[case::signing_filter_any(
1762 NetHsmUserMapping::Signing {
1763 backend_user: "signing".parse()?,
1764 signing_key_id: "signing1".parse()?,
1765 key_setup: SigningKeySetup::new(
1766 KeyType::Curve25519,
1767 vec![KeyMechanism::EdDsaSignature],
1768 None,
1769 SignatureType::EdDsa,
1770 CryptographicKeyContext::OpenPgp {
1771 user_ids: OpenPgpUserIdList::new(vec![
1772 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1773 ])?,
1774 version: "v4".parse()?,
1775 },
1776 )?,
1777 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1778 system_user: "signing-user".parse()?,
1779 tag: "signing1".to_string(),
1780 },
1781 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1782 vec!["signing"]
1783 )]
1784 #[case::signing_filter_non_admin(
1785 NetHsmUserMapping::Signing {
1786 backend_user: "signing".parse()?,
1787 signing_key_id: "signing1".parse()?,
1788 key_setup: SigningKeySetup::new(
1789 KeyType::Curve25519,
1790 vec![KeyMechanism::EdDsaSignature],
1791 None,
1792 SignatureType::EdDsa,
1793 CryptographicKeyContext::OpenPgp {
1794 user_ids: OpenPgpUserIdList::new(vec![
1795 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1796 ])?,
1797 version: "v4".parse()?,
1798 },
1799 )?,
1800 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1801 system_user: "signing-user".parse()?,
1802 tag: "signing1".to_string(),
1803 },
1804 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1805 vec!["signing"]
1806 )]
1807 #[case::signing_filter_signing(
1808 NetHsmUserMapping::Signing {
1809 backend_user: "signing".parse()?,
1810 signing_key_id: "signing1".parse()?,
1811 key_setup: SigningKeySetup::new(
1812 KeyType::Curve25519,
1813 vec![KeyMechanism::EdDsaSignature],
1814 None,
1815 SignatureType::EdDsa,
1816 CryptographicKeyContext::OpenPgp {
1817 user_ids: OpenPgpUserIdList::new(vec![
1818 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1819 ])?,
1820 version: "v4".parse()?,
1821 },
1822 )?,
1823 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1824 system_user: "signing-user".parse()?,
1825 tag: "signing1".to_string(),
1826 },
1827 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1828 vec!["signing"]
1829 )]
1830 fn nethsm_user_mapping_backend_user_ids_filter_matches(
1831 #[case] mapping: NetHsmUserMapping,
1832 #[case] filter: BackendUserIdFilter,
1833 #[case] expected: Vec<&str>,
1834 ) -> TestResult {
1835 assert_eq!(mapping.backend_user_ids(filter), expected);
1836
1837 Ok(())
1838 }
1839
1840 #[rstest]
1841 #[case::admin_filter_metrics(
1842 NetHsmUserMapping::Admin("admin".parse()?),
1843 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1844 )]
1845 #[case::admin_filter_non_admin(
1846 NetHsmUserMapping::Admin("admin".parse()?),
1847 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1848 )]
1849 #[case::admin_filter_observer(
1850 NetHsmUserMapping::Admin("admin".parse()?),
1851 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1852 )]
1853 #[case::admin_filter_signing(
1854 NetHsmUserMapping::Admin("admin".parse()?),
1855 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1856 )]
1857 #[case::backup_filter_admin(
1858 NetHsmUserMapping::Backup{
1859 backend_user: "backup".parse()?,
1860 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1861 system_user: "backup-user".parse()?,
1862 },
1863 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1864 )]
1865 #[case::backup_filter_metrics(
1866 NetHsmUserMapping::Backup{
1867 backend_user: "backup".parse()?,
1868 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1869 system_user: "backup-user".parse()?,
1870 },
1871 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1872 )]
1873 #[case::backup_filter_observer(
1874 NetHsmUserMapping::Backup{
1875 backend_user: "backup".parse()?,
1876 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1877 system_user: "backup-user".parse()?,
1878 },
1879 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1880 )]
1881 #[case::backup_filter_signing(
1882 NetHsmUserMapping::Backup{
1883 backend_user: "backup".parse()?,
1884 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1885 system_user: "backup-user".parse()?,
1886 },
1887 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1888 )]
1889 #[case::hermetic_metrics_filter_admin(
1890 NetHsmUserMapping::HermeticMetrics {
1891 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1892 system_user: "metrics-user".parse()?,
1893 },
1894 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1895 )]
1896 #[case::hermetic_metrics_filter_backup(
1897 NetHsmUserMapping::HermeticMetrics {
1898 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1899 system_user: "metrics-user".parse()?,
1900 },
1901 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1902 )]
1903 #[case::hermetic_metrics_filter_signing(
1904 NetHsmUserMapping::HermeticMetrics {
1905 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1906 system_user: "metrics-user".parse()?,
1907 },
1908 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1909 )]
1910 #[case::metrics_filter_admin(
1911 NetHsmUserMapping::Metrics {
1912 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1913 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1914 system_user: "hermetic-metrics-user".parse()?,
1915 },
1916 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1917 )]
1918 #[case::metrics_filter_backup(
1919 NetHsmUserMapping::Metrics {
1920 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1921 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1922 system_user: "hermetic-metrics-user".parse()?,
1923 },
1924 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1925 )]
1926 #[case::metrics_filter_signing(
1927 NetHsmUserMapping::Metrics {
1928 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1929 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1930 system_user: "hermetic-metrics-user".parse()?,
1931 },
1932 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1933 )]
1934 #[case::signing_filter_admin(
1935 NetHsmUserMapping::Signing {
1936 backend_user: "signing".parse()?,
1937 signing_key_id: "signing1".parse()?,
1938 key_setup: SigningKeySetup::new(
1939 KeyType::Curve25519,
1940 vec![KeyMechanism::EdDsaSignature],
1941 None,
1942 SignatureType::EdDsa,
1943 CryptographicKeyContext::OpenPgp {
1944 user_ids: OpenPgpUserIdList::new(vec![
1945 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1946 ])?,
1947 version: "v4".parse()?,
1948 },
1949 )?,
1950 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1951 system_user: "signing-user".parse()?,
1952 tag: "signing1".to_string(),
1953 },
1954 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1955 )]
1956 #[case::signing_filter_backup(
1957 NetHsmUserMapping::Signing {
1958 backend_user: "signing".parse()?,
1959 signing_key_id: "signing1".parse()?,
1960 key_setup: SigningKeySetup::new(
1961 KeyType::Curve25519,
1962 vec![KeyMechanism::EdDsaSignature],
1963 None,
1964 SignatureType::EdDsa,
1965 CryptographicKeyContext::OpenPgp {
1966 user_ids: OpenPgpUserIdList::new(vec![
1967 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1968 ])?,
1969 version: "v4".parse()?,
1970 },
1971 )?,
1972 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1973 system_user: "signing-user".parse()?,
1974 tag: "signing1".to_string(),
1975 },
1976 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1977 )]
1978 #[case::signing_filter_metrics(
1979 NetHsmUserMapping::Signing {
1980 backend_user: "signing".parse()?,
1981 signing_key_id: "signing1".parse()?,
1982 key_setup: SigningKeySetup::new(
1983 KeyType::Curve25519,
1984 vec![KeyMechanism::EdDsaSignature],
1985 None,
1986 SignatureType::EdDsa,
1987 CryptographicKeyContext::OpenPgp {
1988 user_ids: OpenPgpUserIdList::new(vec![
1989 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1990 ])?,
1991 version: "v4".parse()?,
1992 },
1993 )?,
1994 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1995 system_user: "signing-user".parse()?,
1996 tag: "signing1".to_string(),
1997 },
1998 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1999 )]
2000 #[case::signing_filter_observer(
2001 NetHsmUserMapping::Signing {
2002 backend_user: "signing".parse()?,
2003 signing_key_id: "signing1".parse()?,
2004 key_setup: SigningKeySetup::new(
2005 KeyType::Curve25519,
2006 vec![KeyMechanism::EdDsaSignature],
2007 None,
2008 SignatureType::EdDsa,
2009 CryptographicKeyContext::OpenPgp {
2010 user_ids: OpenPgpUserIdList::new(vec![
2011 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2012 ])?,
2013 version: "v4".parse()?,
2014 },
2015 )?,
2016 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2017 system_user: "signing-user".parse()?,
2018 tag: "signing1".to_string(),
2019 },
2020 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2021 )]
2022 fn nethsm_user_mapping_backend_user_ids_filter_mismatches(
2023 #[case] mapping: NetHsmUserMapping,
2024 #[case] filter: BackendUserIdFilter,
2025 ) -> TestResult {
2026 assert!(mapping.backend_user_ids(filter).is_empty());
2027
2028 Ok(())
2029 }
2030
2031 #[test]
2032 fn nethsm_user_mapping_backend_user_with_passphrase_succeeds() -> TestResult {
2033 let mapping = NetHsmUserMapping::Admin("admin".parse()?);
2034 let passphrase = Passphrase::generate(None);
2035 let creds = mapping.backend_user_with_passphrase("admin", passphrase.clone())?;
2036
2037 assert_eq!(creds.user(), "admin");
2038 assert_eq!(
2039 creds.passphrase().expose_borrowed(),
2040 passphrase.expose_borrowed()
2041 );
2042
2043 Ok(())
2044 }
2045
2046 #[rstest]
2047 #[case::admin_filter_admin(
2048 NetHsmUserMapping::Admin("admin".parse()?),
2049 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2050 vec!["admin"]
2051 )]
2052 #[case::admin_filter_any(
2053 NetHsmUserMapping::Admin("admin".parse()?),
2054 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2055 vec!["admin"]
2056 )]
2057 #[case::backup_filter_any(
2058 NetHsmUserMapping::Backup{
2059 backend_user: "backup".parse()?,
2060 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2061 system_user: "backup-user".parse()?,
2062 },
2063 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2064 vec!["backup"]
2065 )]
2066 #[case::backup_filter_backup(
2067 NetHsmUserMapping::Backup{
2068 backend_user: "backup".parse()?,
2069 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2070 system_user: "backup-user".parse()?,
2071 },
2072 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2073 vec!["backup"]
2074 )]
2075 #[case::backup_filter_non_admin(
2076 NetHsmUserMapping::Backup{
2077 backend_user: "backup".parse()?,
2078 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2079 system_user: "backup-user".parse()?,
2080 },
2081 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2082 vec!["backup"]
2083 )]
2084 #[case::hermetic_metrics_filter_any(
2085 NetHsmUserMapping::HermeticMetrics {
2086 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2087 system_user: "metrics-user".parse()?,
2088 },
2089 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2090 vec!["metrics", "keymetrics"]
2091 )]
2092 #[case::hermetic_metrics_filter_metrics(
2093 NetHsmUserMapping::HermeticMetrics {
2094 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2095 system_user: "metrics-user".parse()?,
2096 },
2097 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2098 vec!["metrics"]
2099 )]
2100 #[case::hermetic_metrics_filter_non_admin(
2101 NetHsmUserMapping::HermeticMetrics {
2102 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2103 system_user: "metrics-user".parse()?,
2104 },
2105 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2106 vec!["metrics", "keymetrics"]
2107 )]
2108 #[case::hermetic_metrics_filter_observer(
2109 NetHsmUserMapping::HermeticMetrics {
2110 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2111 system_user: "metrics-user".parse()?,
2112 },
2113 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2114 vec!["keymetrics"]
2115 )]
2116 #[case::signing_filter_any(
2117 NetHsmUserMapping::Signing {
2118 backend_user: "signing".parse()?,
2119 signing_key_id: "signing1".parse()?,
2120 key_setup: SigningKeySetup::new(
2121 KeyType::Curve25519,
2122 vec![KeyMechanism::EdDsaSignature],
2123 None,
2124 SignatureType::EdDsa,
2125 CryptographicKeyContext::OpenPgp {
2126 user_ids: OpenPgpUserIdList::new(vec![
2127 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2128 ])?,
2129 version: "v4".parse()?,
2130 },
2131 )?,
2132 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2133 system_user: "signing-user".parse()?,
2134 tag: "signing1".to_string(),
2135 },
2136 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2137 vec!["signing"]
2138 )]
2139 #[case::signing_filter_non_admin(
2140 NetHsmUserMapping::Signing {
2141 backend_user: "signing".parse()?,
2142 signing_key_id: "signing1".parse()?,
2143 key_setup: SigningKeySetup::new(
2144 KeyType::Curve25519,
2145 vec![KeyMechanism::EdDsaSignature],
2146 None,
2147 SignatureType::EdDsa,
2148 CryptographicKeyContext::OpenPgp {
2149 user_ids: OpenPgpUserIdList::new(vec![
2150 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2151 ])?,
2152 version: "v4".parse()?,
2153 },
2154 )?,
2155 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2156 system_user: "signing-user".parse()?,
2157 tag: "signing1".to_string(),
2158 },
2159 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2160 vec!["signing"]
2161 )]
2162 #[case::signing_filter_signing(
2163 NetHsmUserMapping::Signing {
2164 backend_user: "signing".parse()?,
2165 signing_key_id: "signing1".parse()?,
2166 key_setup: SigningKeySetup::new(
2167 KeyType::Curve25519,
2168 vec![KeyMechanism::EdDsaSignature],
2169 None,
2170 SignatureType::EdDsa,
2171 CryptographicKeyContext::OpenPgp {
2172 user_ids: OpenPgpUserIdList::new(vec![
2173 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2174 ])?,
2175 version: "v4".parse()?,
2176 },
2177 )?,
2178 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2179 system_user: "signing-user".parse()?,
2180 tag: "signing1".to_string(),
2181 },
2182 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2183 vec!["signing"]
2184 )]
2185
2186 fn nethsm_user_mapping_backend_users_with_new_passphrase_filter_matches(
2187 #[case] mapping: NetHsmUserMapping,
2188 #[case] filter: BackendUserIdFilter,
2189 #[case] expected: Vec<&str>,
2190 ) -> TestResult {
2191 let creds = mapping.backend_users_with_new_passphrase(filter);
2192 let users = creds
2193 .iter()
2194 .map(|creds| creds.user())
2195 .collect::<HashSet<_>>();
2196 let expected = expected
2197 .iter()
2198 .map(ToString::to_string)
2199 .collect::<HashSet<_>>();
2200 assert_eq!(users, expected);
2201
2202 Ok(())
2203 }
2204
2205 #[rstest]
2206 #[case::admin_filter_metrics(
2207 NetHsmUserMapping::Admin("admin".parse()?),
2208 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2209 )]
2210 #[case::admin_filter_non_admin(
2211 NetHsmUserMapping::Admin("admin".parse()?),
2212 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2213 )]
2214 #[case::admin_filter_observer(
2215 NetHsmUserMapping::Admin("admin".parse()?),
2216 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2217 )]
2218 #[case::admin_filter_signing(
2219 NetHsmUserMapping::Admin("admin".parse()?),
2220 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2221 )]
2222 #[case::backup_filter_admin(
2223 NetHsmUserMapping::Backup{
2224 backend_user: "backup".parse()?,
2225 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2226 system_user: "backup-user".parse()?,
2227 },
2228 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2229 )]
2230 #[case::backup_filter_metrics(
2231 NetHsmUserMapping::Backup{
2232 backend_user: "backup".parse()?,
2233 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2234 system_user: "backup-user".parse()?,
2235 },
2236 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2237 )]
2238 #[case::backup_filter_observer(
2239 NetHsmUserMapping::Backup{
2240 backend_user: "backup".parse()?,
2241 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2242 system_user: "backup-user".parse()?,
2243 },
2244 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2245 )]
2246 #[case::backup_filter_signing(
2247 NetHsmUserMapping::Backup{
2248 backend_user: "backup".parse()?,
2249 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2250 system_user: "backup-user".parse()?,
2251 },
2252 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2253 )]
2254 #[case::hermetic_metrics_filter_admin(
2255 NetHsmUserMapping::HermeticMetrics {
2256 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2257 system_user: "metrics-user".parse()?,
2258 },
2259 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2260 )]
2261 #[case::hermetic_metrics_filter_backup(
2262 NetHsmUserMapping::HermeticMetrics {
2263 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2264 system_user: "metrics-user".parse()?,
2265 },
2266 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2267 )]
2268 #[case::hermetic_metrics_filter_signing(
2269 NetHsmUserMapping::HermeticMetrics {
2270 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2271 system_user: "metrics-user".parse()?,
2272 },
2273 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2274 )]
2275 #[case::metrics_filter_admin(
2276 NetHsmUserMapping::Metrics {
2277 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2278 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2279 system_user: "hermetic-metrics-user".parse()?,
2280 },
2281 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2282 )]
2283 #[case::metrics_filter_backup(
2284 NetHsmUserMapping::Metrics {
2285 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2286 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2287 system_user: "hermetic-metrics-user".parse()?,
2288 },
2289 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2290 )]
2291 #[case::metrics_filter_signing(
2292 NetHsmUserMapping::Metrics {
2293 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2294 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2295 system_user: "hermetic-metrics-user".parse()?,
2296 },
2297 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2298 )]
2299 #[case::signing_filter_admin(
2300 NetHsmUserMapping::Signing {
2301 backend_user: "signing".parse()?,
2302 signing_key_id: "signing1".parse()?,
2303 key_setup: SigningKeySetup::new(
2304 KeyType::Curve25519,
2305 vec![KeyMechanism::EdDsaSignature],
2306 None,
2307 SignatureType::EdDsa,
2308 CryptographicKeyContext::OpenPgp {
2309 user_ids: OpenPgpUserIdList::new(vec![
2310 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2311 ])?,
2312 version: "v4".parse()?,
2313 },
2314 )?,
2315 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2316 system_user: "signing-user".parse()?,
2317 tag: "signing1".to_string(),
2318 },
2319 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2320 )]
2321 #[case::signing_filter_backup(
2322 NetHsmUserMapping::Signing {
2323 backend_user: "signing".parse()?,
2324 signing_key_id: "signing1".parse()?,
2325 key_setup: SigningKeySetup::new(
2326 KeyType::Curve25519,
2327 vec![KeyMechanism::EdDsaSignature],
2328 None,
2329 SignatureType::EdDsa,
2330 CryptographicKeyContext::OpenPgp {
2331 user_ids: OpenPgpUserIdList::new(vec![
2332 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2333 ])?,
2334 version: "v4".parse()?,
2335 },
2336 )?,
2337 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2338 system_user: "signing-user".parse()?,
2339 tag: "signing1".to_string(),
2340 },
2341 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2342 )]
2343 #[case::signing_filter_metrics(
2344 NetHsmUserMapping::Signing {
2345 backend_user: "signing".parse()?,
2346 signing_key_id: "signing1".parse()?,
2347 key_setup: SigningKeySetup::new(
2348 KeyType::Curve25519,
2349 vec![KeyMechanism::EdDsaSignature],
2350 None,
2351 SignatureType::EdDsa,
2352 CryptographicKeyContext::OpenPgp {
2353 user_ids: OpenPgpUserIdList::new(vec![
2354 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2355 ])?,
2356 version: "v4".parse()?,
2357 },
2358 )?,
2359 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2360 system_user: "signing-user".parse()?,
2361 tag: "signing1".to_string(),
2362 },
2363 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2364 )]
2365 #[case::signing_filter_observer(
2366 NetHsmUserMapping::Signing {
2367 backend_user: "signing".parse()?,
2368 signing_key_id: "signing1".parse()?,
2369 key_setup: SigningKeySetup::new(
2370 KeyType::Curve25519,
2371 vec![KeyMechanism::EdDsaSignature],
2372 None,
2373 SignatureType::EdDsa,
2374 CryptographicKeyContext::OpenPgp {
2375 user_ids: OpenPgpUserIdList::new(vec![
2376 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2377 ])?,
2378 version: "v4".parse()?,
2379 },
2380 )?,
2381 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2382 system_user: "signing-user".parse()?,
2383 tag: "signing1".to_string(),
2384 },
2385 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2386 )]
2387 fn nethsm_user_mapping_backend_users_with_new_passphrase_filter_mismatches(
2388 #[case] mapping: NetHsmUserMapping,
2389 #[case] filter: BackendUserIdFilter,
2390 ) -> TestResult {
2391 assert!(mapping.backend_users_with_new_passphrase(filter).is_empty());
2392
2393 Ok(())
2394 }
2395
2396 #[test]
2397 fn nethsm_user_mapping_backend_user_with_passphrase_fails() -> TestResult {
2398 let mapping = NetHsmUserMapping::Admin("admin".parse()?);
2399 assert!(
2400 mapping
2401 .backend_user_with_passphrase("2", Passphrase::generate(None))
2402 .is_err()
2403 );
2404
2405 Ok(())
2406 }
2407
2408 #[fixture]
2409 fn nethsm_config_connections() -> TestResult<BTreeSet<Connection>> {
2410 Ok(BTreeSet::from_iter([
2411 Connection::new(
2412 "https://nethsm1.example.org/".parse()?,
2413 nethsm::ConnectionSecurity::Unsafe,
2414 ),
2415 Connection::new(
2416 "https://nethsm2.example.org/".parse()?,
2417 nethsm::ConnectionSecurity::Unsafe,
2418 ),
2419 ]))
2420 }
2421
2422 #[fixture]
2423 fn nethsm_config_mappings() -> TestResult<BTreeSet<NetHsmUserMapping>> {
2424 Ok(BTreeSet::from_iter([
2425 NetHsmUserMapping::Admin("admin".parse()?),
2426 NetHsmUserMapping::Backup{
2427 backend_user: "backup".parse()?,
2428 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2429 system_user: "backup-user".parse()?,
2430 },
2431 NetHsmUserMapping::HermeticMetrics {
2432 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2433 system_user: "metrics-user".parse()?,
2434 },
2435 NetHsmUserMapping::Metrics {
2436 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2437 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2438 system_user: "hermetic-metrics-user".parse()?,
2439 },
2440 NetHsmUserMapping::Signing {
2441 backend_user: "signing".parse()?,
2442 signing_key_id: "signing1".parse()?,
2443 key_setup: SigningKeySetup::new(
2444 KeyType::Curve25519,
2445 vec![KeyMechanism::EdDsaSignature],
2446 None,
2447 SignatureType::EdDsa,
2448 CryptographicKeyContext::OpenPgp {
2449 user_ids: OpenPgpUserIdList::new(vec![
2450 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2451 ])?,
2452 version: "v4".parse()?,
2453 },
2454 )?,
2455 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2456 system_user: "signing-user".parse()?,
2457 tag: "signing1".to_string(),
2458 }
2459 ]))
2460 }
2461
2462 #[fixture]
2463 fn nethsm_config(
2464 nethsm_config_connections: TestResult<BTreeSet<Connection>>,
2465 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2466 ) -> TestResult<NetHsmConfig> {
2467 let nethsm_config_connections = nethsm_config_connections?;
2468 let nethsm_config_mappings = nethsm_config_mappings?;
2469 Ok(NetHsmConfig::new(
2470 nethsm_config_connections,
2471 nethsm_config_mappings,
2472 )?)
2473 }
2474
2475 #[rstest]
2476 fn nethsm_config_connections_matches(
2477 nethsm_config: TestResult<NetHsmConfig>,
2478 nethsm_config_connections: TestResult<BTreeSet<Connection>>,
2479 ) -> TestResult {
2480 let nethsm_config = nethsm_config?;
2481 let nethsm_config_connections = nethsm_config_connections?;
2482 assert_eq!(nethsm_config.connections(), &nethsm_config_connections);
2483
2484 Ok(())
2485 }
2486
2487 #[rstest]
2488 fn nethsm_config_mappings_matches(
2489 nethsm_config: TestResult<NetHsmConfig>,
2490 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2491 ) -> TestResult {
2492 let nethsm_config = nethsm_config?;
2493 let nethsm_config_mappings = nethsm_config_mappings?;
2494 assert_eq!(nethsm_config.mappings(), &nethsm_config_mappings);
2495
2496 Ok(())
2497 }
2498
2499 #[rstest]
2500 fn nethsm_config_authorized_key_entries(
2501 nethsm_config: TestResult<NetHsmConfig>,
2502 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2503 ) -> TestResult {
2504 let nethsm_config = nethsm_config?;
2505 let nethsm_config_mappings = nethsm_config_mappings?;
2506 let expected = nethsm_config_mappings
2507 .iter()
2508 .filter_map(|mapping| mapping.authorized_key_entry())
2509 .collect::<HashSet<_>>();
2510 assert_eq!(nethsm_config.authorized_key_entries(), expected);
2511
2512 Ok(())
2513 }
2514
2515 #[rstest]
2516 fn nethsm_config_system_user_ids(
2517 nethsm_config: TestResult<NetHsmConfig>,
2518 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2519 ) -> TestResult {
2520 let nethsm_config = nethsm_config?;
2521 let nethsm_config_mappings = nethsm_config_mappings?;
2522 let expected = nethsm_config_mappings
2523 .iter()
2524 .filter_map(|mapping| mapping.system_user_id())
2525 .collect::<HashSet<_>>();
2526 assert_eq!(nethsm_config.system_user_ids(), expected);
2527
2528 Ok(())
2529 }
2530
2531 #[rstest]
2532 #[case::no_connection(
2533 "Error message for NetHsmConfig::new with no backend connection",
2534 BTreeSet::new(),
2535 BTreeSet::from_iter([
2536 NetHsmUserMapping::Admin("admin".parse()?),
2537 NetHsmUserMapping::Backup{
2538 backend_user: "backup".parse()?,
2539 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2540 system_user: "backup-user".parse()?,
2541 },
2542 NetHsmUserMapping::HermeticMetrics {
2543 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2544 system_user: "hermetic-metrics-user".parse()?,
2545 },
2546 NetHsmUserMapping::Metrics {
2547 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2548 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2549 system_user: "metrics-user".parse()?,
2550 },
2551 NetHsmUserMapping::Signing {
2552 backend_user: "signing".parse()?,
2553 signing_key_id: "signing1".parse()?,
2554 key_setup: SigningKeySetup::new(
2555 KeyType::Curve25519,
2556 vec![KeyMechanism::EdDsaSignature],
2557 None,
2558 SignatureType::EdDsa,
2559 CryptographicKeyContext::OpenPgp {
2560 user_ids: OpenPgpUserIdList::new(vec![
2561 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2562 ])?,
2563 version: "v4".parse()?,
2564 },
2565 )?,
2566 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2567 system_user: "signing-user".parse()?,
2568 tag: "signing1".to_string(),
2569 }
2570 ]),
2571 )]
2572 #[case::duplicate_connection_url(
2573 "Error message for NetHsmConfig::new with two duplicate connection URLs",
2574 BTreeSet::from_iter([
2575 Connection::new("https://nethsm1.example.org/".parse()?, nethsm::ConnectionSecurity::Unsafe),
2576 Connection::new("https://nethsm1.example.org/".parse()?, nethsm::ConnectionSecurity::Native),
2577 ]),
2578 BTreeSet::from_iter([
2579 NetHsmUserMapping::Admin("admin".parse()?),
2580 NetHsmUserMapping::Backup{
2581 backend_user: "backup".parse()?,
2582 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2583 system_user: "backup-user".parse()?,
2584 },
2585 NetHsmUserMapping::HermeticMetrics {
2586 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2587 system_user: "hermetic-metrics-user".parse()?,
2588 },
2589 NetHsmUserMapping::Metrics {
2590 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2591 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2592 system_user: "metrics-user".parse()?,
2593 },
2594 NetHsmUserMapping::Signing {
2595 backend_user: "signing".parse()?,
2596 signing_key_id: "signing1".parse()?,
2597 key_setup: SigningKeySetup::new(
2598 KeyType::Curve25519,
2599 vec![KeyMechanism::EdDsaSignature],
2600 None,
2601 SignatureType::EdDsa,
2602 CryptographicKeyContext::OpenPgp {
2603 user_ids: OpenPgpUserIdList::new(vec![
2604 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2605 ])?,
2606 version: "v4".parse()?,
2607 },
2608 )?,
2609 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2610 system_user: "signing-user".parse()?,
2611 tag: "signing1".to_string(),
2612 }
2613 ]),
2614 )]
2615 #[case::no_mappings(
2616 "Error message for NetHsmConfig::new with no user mappings",
2617 BTreeSet::from_iter([
2618 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2619 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2620 ]),
2621 BTreeSet::new(),
2622 )]
2623 #[case::duplicate_system_user_ids(
2624 "Error message for NetHsmConfig::new with two duplicate system user IDs",
2625 BTreeSet::from_iter([
2626 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2627 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2628 ]),
2629 BTreeSet::from_iter([
2630 NetHsmUserMapping::Admin("admin".parse()?),
2631 NetHsmUserMapping::Backup{
2632 backend_user: "backup".parse()?,
2633 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2634 system_user: "backup-user".parse()?,
2635 },
2636 NetHsmUserMapping::HermeticMetrics {
2637 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2638 system_user: "hermetic-metrics-user".parse()?,
2639 },
2640 NetHsmUserMapping::Metrics {
2641 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2642 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2643 system_user: "backup-user".parse()?,
2644 },
2645 NetHsmUserMapping::Signing {
2646 backend_user: "signing".parse()?,
2647 signing_key_id: "signing1".parse()?,
2648 key_setup: SigningKeySetup::new(
2649 KeyType::Curve25519,
2650 vec![KeyMechanism::EdDsaSignature],
2651 None,
2652 SignatureType::EdDsa,
2653 CryptographicKeyContext::OpenPgp {
2654 user_ids: OpenPgpUserIdList::new(vec![
2655 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2656 ])?,
2657 version: "v4".parse()?,
2658 },
2659 )?,
2660 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2661 system_user: "signing-user".parse()?,
2662 tag: "signing1".to_string(),
2663 }
2664 ]),
2665 )]
2666 #[case::duplicate_ssh_public_keys(
2667 "Error message for NetHsmConfig::new with two duplicate SSH public keys as authorized keys",
2668 BTreeSet::from_iter([
2669 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2670 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2671 ]),
2672 BTreeSet::from_iter([
2673 NetHsmUserMapping::Admin("admin".parse()?),
2674 NetHsmUserMapping::Backup{
2675 backend_user: "backup".parse()?,
2676 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2677 system_user: "backup-user".parse()?,
2678 },
2679 NetHsmUserMapping::HermeticMetrics {
2680 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2681 system_user: "hermetic-metrics-user".parse()?,
2682 },
2683 NetHsmUserMapping::Metrics {
2684 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2685 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user2@other-host".parse()?,
2686 system_user: "metrics-user".parse()?,
2687 },
2688 NetHsmUserMapping::Signing {
2689 backend_user: "signing".parse()?,
2690 signing_key_id: "signing1".parse()?,
2691 key_setup: SigningKeySetup::new(
2692 KeyType::Curve25519,
2693 vec![KeyMechanism::EdDsaSignature],
2694 None,
2695 SignatureType::EdDsa,
2696 CryptographicKeyContext::OpenPgp {
2697 user_ids: OpenPgpUserIdList::new(vec![
2698 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2699 ])?,
2700 version: "v4".parse()?,
2701 },
2702 )?,
2703 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2704 system_user: "signing-user".parse()?,
2705 tag: "signing1".to_string(),
2706 }
2707 ]),
2708 )]
2709 #[case::missing_system_wide_administrator(
2710 "Error message for NetHsmConfig::new with a system-wide administrator missing",
2711 BTreeSet::from_iter([
2712 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2713 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2714 ]),
2715 BTreeSet::from_iter([
2716 NetHsmUserMapping::Backup{
2717 backend_user: "backup".parse()?,
2718 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2719 system_user: "backup-user".parse()?,
2720 },
2721 NetHsmUserMapping::HermeticMetrics {
2722 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2723 system_user: "hermetic-metrics-user".parse()?,
2724 },
2725 NetHsmUserMapping::Metrics {
2726 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2727 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2728 system_user: "metrics-user".parse()?,
2729 },
2730 NetHsmUserMapping::Signing {
2731 backend_user: "signing".parse()?,
2732 signing_key_id: "signing1".parse()?,
2733 key_setup: SigningKeySetup::new(
2734 KeyType::Curve25519,
2735 vec![KeyMechanism::EdDsaSignature],
2736 None,
2737 SignatureType::EdDsa,
2738 CryptographicKeyContext::OpenPgp {
2739 user_ids: OpenPgpUserIdList::new(vec![
2740 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2741 ])?,
2742 version: "v4".parse()?,
2743 },
2744 )?,
2745 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2746 system_user: "signing-user".parse()?,
2747 tag: "signing1".to_string(),
2748 }
2749 ]),
2750 )]
2751 #[case::duplicate_system_wide_backend_user_ids(
2752 "Error message for NetHsmConfig::new with two duplicate system-wide backend user IDs",
2753 BTreeSet::from_iter([
2754 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2755 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2756 ]),
2757 BTreeSet::from_iter([
2758 NetHsmUserMapping::Admin("admin".parse()?),
2759 NetHsmUserMapping::Admin("backup".parse()?),
2760 NetHsmUserMapping::Backup{
2761 backend_user: "backup".parse()?,
2762 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2763 system_user: "backup-user".parse()?,
2764 },
2765 NetHsmUserMapping::HermeticMetrics {
2766 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2767 system_user: "hermetic-metrics-user".parse()?,
2768 },
2769 NetHsmUserMapping::Metrics {
2770 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2771 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2772 system_user: "metrics-user".parse()?,
2773 },
2774 NetHsmUserMapping::Signing {
2775 backend_user: "signing".parse()?,
2776 signing_key_id: "signing1".parse()?,
2777 key_setup: SigningKeySetup::new(
2778 KeyType::Curve25519,
2779 vec![KeyMechanism::EdDsaSignature],
2780 None,
2781 SignatureType::EdDsa,
2782 CryptographicKeyContext::OpenPgp {
2783 user_ids: OpenPgpUserIdList::new(vec![
2784 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2785 ])?,
2786 version: "v4".parse()?,
2787 },
2788 )?,
2789 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2790 system_user: "signing-user".parse()?,
2791 tag: "signing1".to_string(),
2792 }
2793 ]),
2794 )]
2795 #[case::duplicate_namespaced_backend_user_ids(
2796 "Error message for NetHsmConfig::new with two duplicate namespaced backend user IDs",
2797 BTreeSet::from_iter([
2798 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2799 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2800 ]),
2801 BTreeSet::from_iter([
2802 NetHsmUserMapping::Admin("admin".parse()?),
2803 NetHsmUserMapping::Admin("ns1~admin".parse()?),
2804 NetHsmUserMapping::Signing {
2805 backend_user: "ns1~signing1".parse()?,
2806 signing_key_id: "signing1".parse()?,
2807 key_setup: SigningKeySetup::new(
2808 KeyType::Curve25519,
2809 vec![KeyMechanism::EdDsaSignature],
2810 None,
2811 SignatureType::EdDsa,
2812 CryptographicKeyContext::OpenPgp {
2813 user_ids: OpenPgpUserIdList::new(vec![
2814 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2815 ])?,
2816 version: "v4".parse()?,
2817 },
2818 )?,
2819 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2820 system_user: "ns1-signing-user1".parse()?,
2821 tag: "signing1".to_string(),
2822 },
2823 NetHsmUserMapping::Signing {
2824 backend_user: "ns1~signing1".parse()?,
2825 signing_key_id: "signing2".parse()?,
2826 key_setup: SigningKeySetup::new(
2827 KeyType::Curve25519,
2828 vec![KeyMechanism::EdDsaSignature],
2829 None,
2830 SignatureType::EdDsa,
2831 CryptographicKeyContext::OpenPgp {
2832 user_ids: OpenPgpUserIdList::new(vec![
2833 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2834 ])?,
2835 version: "v4".parse()?,
2836 },
2837 )?,
2838 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2839 system_user: "ns1-signing-user2".parse()?,
2840 tag: "signing2".to_string(),
2841 }
2842 ]),
2843 )]
2844 #[case::duplicate_system_wide_key_ids(
2845 "Error message for NetHsmConfig::new with two duplicate system-wide key IDs",
2846 BTreeSet::from_iter([
2847 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2848 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2849 ]),
2850 BTreeSet::from_iter([
2851 NetHsmUserMapping::Admin("admin".parse()?),
2852 NetHsmUserMapping::Backup{
2853 backend_user: "backup".parse()?,
2854 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2855 system_user: "backup-user".parse()?,
2856 },
2857 NetHsmUserMapping::HermeticMetrics {
2858 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2859 system_user: "hermetic-metrics-user".parse()?,
2860 },
2861 NetHsmUserMapping::Metrics {
2862 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2863 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2864 system_user: "metrics-user".parse()?,
2865 },
2866 NetHsmUserMapping::Signing {
2867 backend_user: "signing1".parse()?,
2868 signing_key_id: "signing1".parse()?,
2869 key_setup: SigningKeySetup::new(
2870 KeyType::Curve25519,
2871 vec![KeyMechanism::EdDsaSignature],
2872 None,
2873 SignatureType::EdDsa,
2874 CryptographicKeyContext::OpenPgp {
2875 user_ids: OpenPgpUserIdList::new(vec![
2876 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2877 ])?,
2878 version: "v4".parse()?,
2879 },
2880 )?,
2881 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2882 system_user: "signing-user".parse()?,
2883 tag: "signing1".to_string(),
2884 },
2885 NetHsmUserMapping::Signing {
2886 backend_user: "signing2".parse()?,
2887 signing_key_id: "signing1".parse()?,
2888 key_setup: SigningKeySetup::new(
2889 KeyType::Curve25519,
2890 vec![KeyMechanism::EdDsaSignature],
2891 None,
2892 SignatureType::EdDsa,
2893 CryptographicKeyContext::OpenPgp {
2894 user_ids: OpenPgpUserIdList::new(vec![
2895 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2896 ])?,
2897 version: "v4".parse()?,
2898 },
2899 )?,
2900 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2901 system_user: "signing-user2".parse()?,
2902 tag: "signing2".to_string(),
2903 }
2904 ]),
2905 )]
2906 #[case::duplicate_system_wide_tags(
2907 "Error message for NetHsmConfig::new with two duplicate system-wide tags",
2908 BTreeSet::from_iter([
2909 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2910 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2911 ]),
2912 BTreeSet::from_iter([
2913 NetHsmUserMapping::Admin("admin".parse()?),
2914 NetHsmUserMapping::Backup{
2915 backend_user: "backup".parse()?,
2916 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2917 system_user: "backup-user".parse()?,
2918 },
2919 NetHsmUserMapping::HermeticMetrics {
2920 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2921 system_user: "hermetic-metrics-user".parse()?,
2922 },
2923 NetHsmUserMapping::Metrics {
2924 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2925 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2926 system_user: "metrics-user".parse()?,
2927 },
2928 NetHsmUserMapping::Signing {
2929 backend_user: "signing1".parse()?,
2930 signing_key_id: "signing1".parse()?,
2931 key_setup: SigningKeySetup::new(
2932 KeyType::Curve25519,
2933 vec![KeyMechanism::EdDsaSignature],
2934 None,
2935 SignatureType::EdDsa,
2936 CryptographicKeyContext::OpenPgp {
2937 user_ids: OpenPgpUserIdList::new(vec![
2938 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2939 ])?,
2940 version: "v4".parse()?,
2941 },
2942 )?,
2943 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2944 system_user: "signing-user".parse()?,
2945 tag: "signing1".to_string(),
2946 },
2947 NetHsmUserMapping::Signing {
2948 backend_user: "signing2".parse()?,
2949 signing_key_id: "signing2".parse()?,
2950 key_setup: SigningKeySetup::new(
2951 KeyType::Curve25519,
2952 vec![KeyMechanism::EdDsaSignature],
2953 None,
2954 SignatureType::EdDsa,
2955 CryptographicKeyContext::OpenPgp {
2956 user_ids: OpenPgpUserIdList::new(vec![
2957 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2958 ])?,
2959 version: "v4".parse()?,
2960 },
2961 )?,
2962 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2963 system_user: "signing-user2".parse()?,
2964 tag: "signing1".to_string(),
2965 }
2966 ]),
2967 )]
2968 #[case::missing_namespace_administrator(
2969 "Error message for NetHsmConfig::new with a missing namespace administrator",
2970 BTreeSet::from_iter([
2971 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2972 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2973 ]),
2974 BTreeSet::from_iter([
2975 NetHsmUserMapping::Admin("admin".parse()?),
2976 NetHsmUserMapping::Signing {
2977 backend_user: "ns1~signing1".parse()?,
2978 signing_key_id: "signing1".parse()?,
2979 key_setup: SigningKeySetup::new(
2980 KeyType::Curve25519,
2981 vec![KeyMechanism::EdDsaSignature],
2982 None,
2983 SignatureType::EdDsa,
2984 CryptographicKeyContext::OpenPgp {
2985 user_ids: OpenPgpUserIdList::new(vec![
2986 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2987 ])?,
2988 version: "v4".parse()?,
2989 },
2990 )?,
2991 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2992 system_user: "ns1-signing-user".parse()?,
2993 tag: "signing1".to_string(),
2994 },
2995 ]),
2996 )]
2997 #[case::duplicate_namespace_key_ids(
2998 "Error message for NetHsmConfig::new with two duplicate namespaced key IDs",
2999 BTreeSet::from_iter([
3000 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3001 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3002 ]),
3003 BTreeSet::from_iter([
3004 NetHsmUserMapping::Admin("admin".parse()?),
3005 NetHsmUserMapping::Admin("ns1~admin".parse()?),
3006 NetHsmUserMapping::Signing {
3007 backend_user: "ns1~signing1".parse()?,
3008 signing_key_id: "signing1".parse()?,
3009 key_setup: SigningKeySetup::new(
3010 KeyType::Curve25519,
3011 vec![KeyMechanism::EdDsaSignature],
3012 None,
3013 SignatureType::EdDsa,
3014 CryptographicKeyContext::OpenPgp {
3015 user_ids: OpenPgpUserIdList::new(vec![
3016 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3017 ])?,
3018 version: "v4".parse()?,
3019 },
3020 )?,
3021 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3022 system_user: "ns1-signing-user".parse()?,
3023 tag: "signing1".to_string(),
3024 },
3025 NetHsmUserMapping::Signing {
3026 backend_user: "ns1~signing2".parse()?,
3027 signing_key_id: "signing1".parse()?,
3028 key_setup: SigningKeySetup::new(
3029 KeyType::Curve25519,
3030 vec![KeyMechanism::EdDsaSignature],
3031 None,
3032 SignatureType::EdDsa,
3033 CryptographicKeyContext::OpenPgp {
3034 user_ids: OpenPgpUserIdList::new(vec![
3035 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3036 ])?,
3037 version: "v4".parse()?,
3038 },
3039 )?,
3040 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3041 system_user: "ns1-signing-user2".parse()?,
3042 tag: "signing2".to_string(),
3043 }
3044 ]),
3045 )]
3046 #[case::duplicate_namespace_tags(
3047 "Error message for NetHsmConfig::new with two duplicate namespaced tags",
3048 BTreeSet::from_iter([
3049 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3050 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3051 ]),
3052 BTreeSet::from_iter([
3053 NetHsmUserMapping::Admin("admin".parse()?),
3054 NetHsmUserMapping::Admin("ns1~admin".parse()?),
3055 NetHsmUserMapping::Signing {
3056 backend_user: "ns1~signing1".parse()?,
3057 signing_key_id: "signing1".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 },
3069 )?,
3070 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3071 system_user: "ns1-signing-user".parse()?,
3072 tag: "signing1".to_string(),
3073 },
3074 NetHsmUserMapping::Signing {
3075 backend_user: "ns1~signing2".parse()?,
3076 signing_key_id: "signing2".parse()?,
3077 key_setup: SigningKeySetup::new(
3078 KeyType::Curve25519,
3079 vec![KeyMechanism::EdDsaSignature],
3080 None,
3081 SignatureType::EdDsa,
3082 CryptographicKeyContext::OpenPgp {
3083 user_ids: OpenPgpUserIdList::new(vec![
3084 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3085 ])?,
3086 version: "v4".parse()?,
3087 },
3088 )?,
3089 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3090 system_user: "ns1-signing-user2".parse()?,
3091 tag: "signing1".to_string(),
3092 }
3093 ]),
3094 )]
3095 #[case::all_the_issues(
3096 "Error message for NetHsmConfig::new with multiple validation issues (connections and mappings)",
3097 BTreeSet::from_iter([
3098 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3099 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Native),
3100 ]),
3101 BTreeSet::from_iter([
3102 NetHsmUserMapping::Signing {
3103 backend_user: "signing1".parse()?,
3104 signing_key_id: "signing1".parse()?,
3105 key_setup: SigningKeySetup::new(
3106 KeyType::Curve25519,
3107 vec![KeyMechanism::EdDsaSignature],
3108 None,
3109 SignatureType::EdDsa,
3110 CryptographicKeyContext::OpenPgp {
3111 user_ids: OpenPgpUserIdList::new(vec![
3112 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3113 ])?,
3114 version: "v4".parse()?,
3115 },
3116 )?,
3117 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3118 system_user: "ns1-signing-user1".parse()?,
3119 tag: "signing1".to_string(),
3120 },
3121 NetHsmUserMapping::Signing {
3122 backend_user: "signing1".parse()?,
3123 signing_key_id: "signing1".parse()?,
3124 key_setup: SigningKeySetup::new(
3125 KeyType::Curve25519,
3126 vec![KeyMechanism::EdDsaSignature],
3127 None,
3128 SignatureType::EdDsa,
3129 CryptographicKeyContext::OpenPgp {
3130 user_ids: OpenPgpUserIdList::new(vec![
3131 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3132 ])?,
3133 version: "v4".parse()?,
3134 },
3135 )?,
3136 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3137 system_user: "ns1-signing-user1".parse()?,
3138 tag: "signing1".to_string(),
3139 },
3140 NetHsmUserMapping::Signing {
3141 backend_user: "ns1~signing1".parse()?,
3142 signing_key_id: "signing1".parse()?,
3143 key_setup: SigningKeySetup::new(
3144 KeyType::Curve25519,
3145 vec![KeyMechanism::EdDsaSignature],
3146 None,
3147 SignatureType::EdDsa,
3148 CryptographicKeyContext::OpenPgp {
3149 user_ids: OpenPgpUserIdList::new(vec![
3150 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3151 ])?,
3152 version: "v4".parse()?,
3153 },
3154 )?,
3155 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3156 system_user: "ns1-signing-user1".parse()?,
3157 tag: "signing1".to_string(),
3158 },
3159 NetHsmUserMapping::Signing {
3160 backend_user: "ns1~signing1".parse()?,
3161 signing_key_id: "signing2".parse()?,
3162 key_setup: SigningKeySetup::new(
3163 KeyType::Curve25519,
3164 vec![KeyMechanism::EdDsaSignature],
3165 None,
3166 SignatureType::EdDsa,
3167 CryptographicKeyContext::OpenPgp {
3168 user_ids: OpenPgpUserIdList::new(vec![
3169 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3170 ])?,
3171 version: "v4".parse()?,
3172 },
3173 )?,
3174 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3175 system_user: "ns1-signing-user2".parse()?,
3176 tag: "signing1".to_string(),
3177 },
3178 NetHsmUserMapping::Signing {
3179 backend_user: "ns1~signing2".parse()?,
3180 signing_key_id: "signing2".parse()?,
3181 key_setup: SigningKeySetup::new(
3182 KeyType::Curve25519,
3183 vec![KeyMechanism::EdDsaSignature],
3184 None,
3185 SignatureType::EdDsa,
3186 CryptographicKeyContext::OpenPgp {
3187 user_ids: OpenPgpUserIdList::new(vec![
3188 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3189 ])?,
3190 version: "v4".parse()?,
3191 },
3192 )?,
3193 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3194 system_user: "ns1-signing-user1".parse()?,
3195 tag: "signing1".to_string(),
3196 },
3197 ]),
3198 )]
3199 fn nethsm_config_new_fails_validation(
3200 #[case] description: &str,
3201 #[case] connections: BTreeSet<Connection>,
3202 #[case] mappings: BTreeSet<NetHsmUserMapping>,
3203 ) -> TestResult {
3204 let error_msg = match NetHsmConfig::new(connections, mappings) {
3205 Err(crate::Error::Validation { source, .. }) => source.to_string(),
3206 Ok(config) => {
3207 panic!("Expected to fail with Error::Validation, but succeeded instead: {config:?}")
3208 }
3209 Err(error) => panic!(
3210 "Expected to fail with Error::Validation, but failed with a different error instead: {error}"
3211 ),
3212 };
3213
3214 with_settings!({
3215 description => description,
3216 snapshot_path => SNAPSHOT_PATH,
3217 prepend_module_to_snapshot => false,
3218 }, {
3219 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_msg);
3220 });
3221 Ok(())
3222 }
3223
3224 #[rstest]
3226 fn nethsm_config_state_from_config(
3227 nethsm_config: TestResult<NetHsmConfig>,
3228 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
3229 ) -> TestResult {
3230 let nethsm_config = nethsm_config?;
3231 let nethsm_config_mappings = nethsm_config_mappings?;
3232 let state = NetHsmConfigState::from(&nethsm_config);
3233
3234 for user_id in nethsm_config_mappings
3235 .iter()
3236 .flat_map(|mapping| mapping.nethsm_user_ids())
3237 {
3238 debug!(
3239 "Ensuring that the NetHSM user ID {user_id} can be found in the NetHSM user state."
3240 );
3241 assert!(
3242 state
3243 .user_data
3244 .iter()
3245 .any(|user_data| user_data.user == &user_id)
3246 );
3247 }
3248
3249 for user_id in nethsm_config_mappings.iter().filter_map(|mapping| {
3250 if let NetHsmUserMapping::Signing { backend_user, .. } = mapping {
3251 Some(backend_user)
3252 } else {
3253 None
3254 }
3255 }) {
3256 debug!(
3257 "Ensuring that the NetHSM user ID {user_id} can be found in the NetHSM key state."
3258 );
3259 assert!(
3260 state
3261 .key_data
3262 .iter()
3263 .any(|user_data| user_data.user == user_id)
3264 );
3265 }
3266
3267 Ok(())
3268 }
3269}