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 = duplicate_domains(
953 &value.iter().collect::<BTreeSet<_>>(),
954 None,
955 Some(" system-wide".to_string()),
956 Some("tag"),
957 );
958
959 let all_namespaces = {
961 let mut all_namespaces = Vec::from_iter(
962 value
963 .iter()
964 .flat_map(|mapping| mapping.namespaces())
965 .collect::<HashSet<_>>(),
966 );
967 all_namespaces.sort();
968 all_namespaces
969 };
970
971 let namespaces_without_admin = {
973 let mut all_namespaces: HashSet<&NamespaceId> = HashSet::from_iter(all_namespaces.clone());
974
975 for mapping in value.iter() {
976 if let NetHsmUserMapping::Admin(user_id) = mapping
977 && let Some(namespace) = user_id.namespace()
978 {
979 all_namespaces.remove(namespace);
980 }
981 }
982
983 if all_namespaces.is_empty() {
984 None
985 } else {
986 let mut namespaces_without_admin = all_namespaces
987 .iter()
988 .map(|namespace| format!("\"{namespace}\""))
989 .collect::<Vec<_>>();
990 namespaces_without_admin.sort();
991 Some(format!(
992 "the namespace{} {} without an administrator user",
993 if namespaces_without_admin.len() > 1 {
994 "s"
995 } else {
996 ""
997 },
998 namespaces_without_admin.join(", ")
999 ))
1000 }
1001 };
1002
1003 let duplicate_namespaced_key_ids = {
1005 let mut all_duplicates = Vec::new();
1006
1007 for namespace in all_namespaces.iter() {
1008 let mut duplicates = duplicate_key_ids(
1009 value,
1010 &NetHsmBackendKeyIdFilter {
1011 namespace: Some(namespace),
1012 },
1013 Some(format!(" \"{namespace}\" namespaced")),
1014 );
1015 if let Some(message) = duplicates.take() {
1016 all_duplicates.push(message)
1017 }
1018 }
1019
1020 if all_duplicates.is_empty() {
1021 None
1022 } else {
1023 Some(all_duplicates.join("\n"))
1024 }
1025 };
1026
1027 let duplicate_namespaced_tags = {
1029 let mut all_duplicates = Vec::new();
1030
1031 for namespace in all_namespaces.iter() {
1032 let mut duplicates = duplicate_domains(
1033 &value.iter().collect::<BTreeSet<_>>(),
1034 Some(&NetHsmConfigDomainFilter {
1035 namespace: Some(namespace),
1036 }),
1037 Some(format!(" \"{namespace}\" namespaced")),
1038 Some("tag"),
1039 );
1040 if let Some(message) = duplicates.take() {
1041 all_duplicates.push(message)
1042 }
1043 }
1044
1045 if all_duplicates.is_empty() {
1046 None
1047 } else {
1048 Some(all_duplicates.join("\n"))
1049 }
1050 };
1051
1052 let messages = [
1053 duplicate_system_user_ids,
1054 duplicate_authorized_keys,
1055 missing_system_wide_admin,
1056 duplicate_backend_user_ids,
1057 duplicate_system_wide_key_ids,
1058 duplicate_system_wide_tags,
1059 namespaces_without_admin,
1060 duplicate_namespaced_key_ids,
1061 duplicate_namespaced_tags,
1062 ];
1063 let error_messages = {
1064 let mut error_messages = Vec::new();
1065
1066 for message in messages.iter().flatten() {
1067 error_messages.push(message.as_str());
1068 }
1069
1070 error_messages
1071 };
1072
1073 match error_messages.len() {
1074 0 => Ok(()),
1075 1 => Err(garde::Error::new(format!(
1076 "contains {}",
1077 error_messages.join("\n")
1078 ))),
1079 _ => Err(garde::Error::new(format!(
1080 "contains multiple issues:\n⤷ {}",
1081 error_messages.join("\n⤷ ")
1082 ))),
1083 }
1084}
1085
1086#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Validate)]
1091#[serde(rename_all = "snake_case")]
1092pub struct NetHsmConfig {
1093 #[garde(custom(validate_nethsm_config_connections))]
1094 connections: BTreeSet<Connection>,
1095
1096 #[garde(custom(validate_nethsm_config_mappings))]
1097 mappings: BTreeSet<NetHsmUserMapping>,
1098}
1099
1100impl NetHsmConfig {
1101 pub fn new(
1104 connections: BTreeSet<Connection>,
1105 mappings: BTreeSet<NetHsmUserMapping>,
1106 ) -> Result<Self, crate::Error> {
1107 let config = Self {
1108 connections,
1109 mappings,
1110 };
1111
1112 config
1113 .validate()
1114 .map_err(|source| crate::Error::Validation {
1115 context: "validating a NetHSM specific configuration item".to_string(),
1116 source,
1117 })?;
1118
1119 Ok(config)
1120 }
1121
1122 pub fn connections(&self) -> &BTreeSet<Connection> {
1124 &self.connections
1125 }
1126
1127 pub fn mappings(&self) -> &BTreeSet<NetHsmUserMapping> {
1129 &self.mappings
1130 }
1131}
1132
1133impl ConfigAuthorizedKeyEntries for NetHsmConfig {
1134 fn authorized_key_entries(&self) -> HashSet<&AuthorizedKeyEntry> {
1135 self.mappings
1136 .iter()
1137 .filter_map(|mapping| mapping.authorized_key_entry())
1138 .collect()
1139 }
1140}
1141
1142impl ConfigSystemUserIds for NetHsmConfig {
1143 fn system_user_ids(&self) -> HashSet<&SystemUserId> {
1144 self.mappings
1145 .iter()
1146 .filter_map(|mapping| mapping.system_user_id())
1147 .collect()
1148 }
1149}
1150
1151#[derive(Debug, Eq, PartialEq)]
1156pub struct NetHsmConfigState<'a> {
1157 pub(crate) user_data: Vec<NetHsmConfigUserData<'a>>,
1159 pub(crate) key_data: Vec<NetHsmConfigUserKeyData<'a>>,
1161}
1162
1163impl<'a> NetHsmConfigState<'a> {
1164 pub const STATE_NAME: &'static str = "NetHSM config";
1166}
1167
1168impl<'a> From<&'a NetHsmConfig> for NetHsmConfigState<'a> {
1169 fn from(value: &'a NetHsmConfig) -> Self {
1170 let (key_data, user_data) = {
1171 let mut key_data = Vec::new();
1172 let mut user_data = Vec::new();
1173
1174 for mapping in value.mappings() {
1175 if let Some(user_key_data) =
1176 mapping.nethsm_config_user_key_data(NetHsmUserKeysFilter::All)
1177 {
1178 key_data.push(user_key_data)
1179 }
1180 user_data.extend(mapping.nethsm_config_user_data());
1181 }
1182
1183 (key_data, user_data)
1184 };
1185
1186 Self {
1187 user_data,
1188 key_data,
1189 }
1190 }
1191}
1192
1193impl<'a> StateOriginInfo for NetHsmConfigState<'a> {
1194 fn state_name(&self) -> &str {
1195 Self::STATE_NAME
1196 }
1197
1198 fn state_origin(&self) -> StateOrigin {
1199 StateOrigin::Config
1200 }
1201}
1202
1203impl<'a> PartialEq<NetHsmBackendState> for NetHsmConfigState<'a> {
1204 fn eq(&self, other: &NetHsmBackendState) -> bool {
1205 if self.user_data.len() != other.user_states.len()
1206 || self.key_data.len() != other.key_states.len()
1207 {
1208 return false;
1209 }
1210
1211 {
1212 let mut remaining_other_user_states: HashSet<&UserState> =
1213 HashSet::from_iter(other.user_states.iter());
1214 'outer: for self_user_data in self.user_data.iter() {
1215 for other_user_state in remaining_other_user_states.iter() {
1216 if self_user_data == *other_user_state {
1217 remaining_other_user_states.remove(*other_user_state);
1218 continue 'outer;
1219 }
1220 }
1221 return false;
1222 }
1223 if !remaining_other_user_states.is_empty() {
1224 return false;
1225 }
1226 }
1227
1228 {
1229 let mut remaining_other_key_states: HashSet<&KeyState> =
1230 HashSet::from_iter(other.key_states.iter());
1231 'outer: for self_key_data in self.key_data.iter() {
1232 for other_user_state in remaining_other_key_states.iter() {
1233 if self_key_data == *other_user_state {
1234 remaining_other_key_states.remove(*other_user_state);
1235 continue 'outer;
1236 }
1237 }
1238 return false;
1239 }
1240 if !remaining_other_key_states.is_empty() {
1241 return false;
1242 }
1243 }
1244
1245 true
1246 }
1247}
1248
1249#[cfg(test)]
1250mod tests {
1251 use std::thread::current;
1252
1253 use insta::{assert_snapshot, with_settings};
1254 use log::debug;
1255 use rstest::{fixture, rstest};
1256 use signstar_crypto::{
1257 key::{CryptographicKeyContext, KeyMechanism, KeyType, SignatureType, SigningKeySetup},
1258 openpgp::OpenPgpUserIdList,
1259 };
1260 use testresult::TestResult;
1261
1262 use super::*;
1263
1264 const SNAPSHOT_PATH: &str = "fixtures/nethsm_config/";
1265
1266 #[test]
1267 fn nethsm_metrics_users_succeeds() -> TestResult {
1268 NetHsmMetricsUsers::new(
1269 SystemWideUserId::new("metrics".to_string())?,
1270 vec![
1271 UserId::new("operator".to_string())?,
1272 UserId::new("ns1~operator".to_string())?,
1273 ],
1274 )?;
1275 Ok(())
1276 }
1277
1278 #[test]
1279 fn nethsm_metrics_users_fails() -> TestResult {
1280 if let Ok(user) = NetHsmMetricsUsers::new(
1281 SystemWideUserId::new("metrics".to_string())?,
1282 vec![
1283 UserId::new("metrics".to_string())?,
1284 UserId::new("ns1~operator".to_string())?,
1285 ],
1286 ) {
1287 panic!("Succeeded creating a NetHsmMetricsUsers, but should have failed:\n{user:?}")
1288 }
1289 Ok(())
1290 }
1291
1292 #[rstest]
1293 #[case::admin(NetHsmUserMapping::Admin("admin".parse()?), vec!["admin".parse()?])]
1294 #[case::backup(
1295 NetHsmUserMapping::Backup{
1296 backend_user: "backup".parse()?,
1297 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1298 system_user: "backup-user".parse()?,
1299 },
1300 vec!["backup".parse()?],
1301 )]
1302 #[case::backup(
1303 NetHsmUserMapping::Metrics{
1304 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1305 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1306 system_user: "metrics-user".parse()?,
1307 },
1308 vec!["metrics".parse()?, "keymetrics".parse()?],
1309 )]
1310 #[case::backup(
1311 NetHsmUserMapping::Signing {
1312 backend_user: "signing".parse()?,
1313 signing_key_id: "signing1".parse()?,
1314 key_setup: SigningKeySetup::new(
1315 KeyType::Curve25519,
1316 vec![KeyMechanism::EdDsaSignature],
1317 None,
1318 SignatureType::EdDsa,
1319 CryptographicKeyContext::OpenPgp {
1320 user_ids: OpenPgpUserIdList::new(vec![
1321 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1322 ])?,
1323 version: "v4".parse()?,
1324 },
1325 )?,
1326 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1327 system_user: "signing-user".parse()?,
1328 tag: "signing1".to_string(),
1329 },
1330 vec!["signing".parse()?],
1331 )]
1332 fn nethsm_user_mapping_nethsm_user_ids(
1333 #[case] mapping: NetHsmUserMapping,
1334 #[case] expected: Vec<UserId>,
1335 ) -> TestResult {
1336 assert_eq!(mapping.nethsm_user_ids(), expected,);
1337
1338 Ok(())
1339 }
1340
1341 #[rstest]
1342 #[case::admin(
1343 NetHsmUserMapping::Admin("admin".parse()?),
1344 vec![("admin".parse()?, UserRole::Administrator, None)],
1345 )]
1346 #[case::backup(
1347 NetHsmUserMapping::Backup{
1348 backend_user: "backup".parse()?,
1349 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1350 system_user: "backup-user".parse()?,
1351 },
1352 vec![("backup".parse()?, UserRole::Backup, None)],
1353 )]
1354 #[case::metrics(
1355 NetHsmUserMapping::Metrics{
1356 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1357 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1358 system_user: "metrics-user".parse()?,
1359 },
1360 vec![
1361 ("keymetrics".parse()?, UserRole::Operator, None),
1362 ("metrics".parse()?, UserRole::Metrics, None),
1363 ],
1364 )]
1365 #[case::signing(
1366 NetHsmUserMapping::Signing {
1367 backend_user: "signing".parse()?,
1368 signing_key_id: "signing1".parse()?,
1369 key_setup: SigningKeySetup::new(
1370 KeyType::Curve25519,
1371 vec![KeyMechanism::EdDsaSignature],
1372 None,
1373 SignatureType::EdDsa,
1374 CryptographicKeyContext::OpenPgp {
1375 user_ids: OpenPgpUserIdList::new(vec![
1376 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1377 ])?,
1378 version: "v4".parse()?,
1379 },
1380 )?,
1381 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1382 system_user: "signing-user".parse()?,
1383 tag: "signing1".to_string(),
1384 },
1385 vec![("signing".parse()?, UserRole::Operator, Some("signing1"))],
1386 )]
1387 fn nethsm_user_mapping_nethsm_config_user_data(
1388 #[case] mapping: NetHsmUserMapping,
1389 #[case] expected: Vec<(UserId, UserRole, Option<&str>)>,
1390 ) -> TestResult {
1391 let expected = expected
1392 .iter()
1393 .map(|(user, role, tag)| NetHsmConfigUserData {
1394 user,
1395 role: *role,
1396 tag: *tag,
1397 })
1398 .collect::<HashSet<_>>();
1399 assert_eq!(mapping.nethsm_config_user_data(), expected);
1400
1401 Ok(())
1402 }
1403
1404 #[rstest]
1405 #[case::admin_filter_all(
1406 NetHsmUserMapping::Admin("admin".parse()?),
1407 NetHsmUserKeysFilter::All,
1408 None,
1409 )]
1410 #[case::admin_filter_namespace(
1411 NetHsmUserMapping::Admin("admin".parse()?),
1412 NetHsmUserKeysFilter::Namespaced,
1413 None,
1414 )]
1415 #[case::admin_filter_system_wide(
1416 NetHsmUserMapping::Admin("admin".parse()?),
1417 NetHsmUserKeysFilter::SystemWide,
1418 None,
1419 )]
1420 #[case::backup_filter_all(
1421 NetHsmUserMapping::Backup{
1422 backend_user: "backup".parse()?,
1423 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1424 system_user: "backup-user".parse()?,
1425 },
1426 NetHsmUserKeysFilter::All,
1427 None,
1428 )]
1429 #[case::backup_filter_namespaced(
1430 NetHsmUserMapping::Backup{
1431 backend_user: "backup".parse()?,
1432 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1433 system_user: "backup-user".parse()?,
1434 },
1435 NetHsmUserKeysFilter::Namespaced,
1436 None,
1437 )]
1438 #[case::backup_filter_system_wide(
1439 NetHsmUserMapping::Backup{
1440 backend_user: "backup".parse()?,
1441 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1442 system_user: "backup-user".parse()?,
1443 },
1444 NetHsmUserKeysFilter::SystemWide,
1445 None,
1446 )]
1447 #[case::metrics_filter_all(
1448 NetHsmUserMapping::Metrics{
1449 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1450 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1451 system_user: "metrics-user".parse()?,
1452 },
1453 NetHsmUserKeysFilter::All,
1454 None,
1455 )]
1456 #[case::metrics_filter_namespaced(
1457 NetHsmUserMapping::Metrics{
1458 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1459 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1460 system_user: "metrics-user".parse()?,
1461 },
1462 NetHsmUserKeysFilter::Namespaced,
1463 None,
1464 )]
1465 #[case::metrics_filter_system_wide(
1466 NetHsmUserMapping::Metrics{
1467 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1468 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1469 system_user: "metrics-user".parse()?,
1470 },
1471 NetHsmUserKeysFilter::SystemWide,
1472 None,
1473 )]
1474 #[case::signing_system_wide_filter_all(
1475 NetHsmUserMapping::Signing {
1476 backend_user: "signing".parse()?,
1477 signing_key_id: "signing1".parse()?,
1478 key_setup: SigningKeySetup::new(
1479 KeyType::Curve25519,
1480 vec![KeyMechanism::EdDsaSignature],
1481 None,
1482 SignatureType::EdDsa,
1483 CryptographicKeyContext::OpenPgp {
1484 user_ids: OpenPgpUserIdList::new(vec![
1485 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1486 ])?,
1487 version: "v4".parse()?,
1488 },
1489 )?,
1490 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1491 system_user: "signing-user".parse()?,
1492 tag: "signing1".to_string(),
1493 },
1494 NetHsmUserKeysFilter::All,
1495 Some((
1496 "signing".parse()?,
1497 "signing1".parse()?,
1498 SigningKeySetup::new(
1499 KeyType::Curve25519,
1500 vec![KeyMechanism::EdDsaSignature],
1501 None,
1502 SignatureType::EdDsa,
1503 CryptographicKeyContext::OpenPgp {
1504 user_ids: OpenPgpUserIdList::new(vec![
1505 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1506 ])?,
1507 version: "v4".parse()?,
1508 },
1509 )?,
1510 "signing1",
1511 )),
1512 )]
1513 #[case::signing_system_wide_filter_namespaced(
1514 NetHsmUserMapping::Signing {
1515 backend_user: "signing".parse()?,
1516 signing_key_id: "signing1".parse()?,
1517 key_setup: SigningKeySetup::new(
1518 KeyType::Curve25519,
1519 vec![KeyMechanism::EdDsaSignature],
1520 None,
1521 SignatureType::EdDsa,
1522 CryptographicKeyContext::OpenPgp {
1523 user_ids: OpenPgpUserIdList::new(vec![
1524 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1525 ])?,
1526 version: "v4".parse()?,
1527 },
1528 )?,
1529 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1530 system_user: "signing-user".parse()?,
1531 tag: "signing1".to_string(),
1532 },
1533 NetHsmUserKeysFilter::Namespaced,
1534 None,
1535 )]
1536 #[case::signing_system_wide_filter_system_wide(
1537 NetHsmUserMapping::Signing {
1538 backend_user: "signing".parse()?,
1539 signing_key_id: "signing1".parse()?,
1540 key_setup: SigningKeySetup::new(
1541 KeyType::Curve25519,
1542 vec![KeyMechanism::EdDsaSignature],
1543 None,
1544 SignatureType::EdDsa,
1545 CryptographicKeyContext::OpenPgp {
1546 user_ids: OpenPgpUserIdList::new(vec![
1547 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1548 ])?,
1549 version: "v4".parse()?,
1550 },
1551 )?,
1552 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1553 system_user: "signing-user".parse()?,
1554 tag: "signing1".to_string(),
1555 },
1556 NetHsmUserKeysFilter::SystemWide,
1557 Some((
1558 "signing".parse()?,
1559 "signing1".parse()?,
1560 SigningKeySetup::new(
1561 KeyType::Curve25519,
1562 vec![KeyMechanism::EdDsaSignature],
1563 None,
1564 SignatureType::EdDsa,
1565 CryptographicKeyContext::OpenPgp {
1566 user_ids: OpenPgpUserIdList::new(vec![
1567 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1568 ])?,
1569 version: "v4".parse()?,
1570 },
1571 )?,
1572 "signing1",
1573 )),
1574 )]
1575 #[case::signing_namespaced_filter_all(
1576 NetHsmUserMapping::Signing {
1577 backend_user: "ns1~signing".parse()?,
1578 signing_key_id: "signing1".parse()?,
1579 key_setup: SigningKeySetup::new(
1580 KeyType::Curve25519,
1581 vec![KeyMechanism::EdDsaSignature],
1582 None,
1583 SignatureType::EdDsa,
1584 CryptographicKeyContext::OpenPgp {
1585 user_ids: OpenPgpUserIdList::new(vec![
1586 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1587 ])?,
1588 version: "v4".parse()?,
1589 },
1590 )?,
1591 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1592 system_user: "signing-user".parse()?,
1593 tag: "signing1".to_string(),
1594 },
1595 NetHsmUserKeysFilter::All,
1596 Some((
1597 "ns1~signing".parse()?,
1598 "signing1".parse()?,
1599 SigningKeySetup::new(
1600 KeyType::Curve25519,
1601 vec![KeyMechanism::EdDsaSignature],
1602 None,
1603 SignatureType::EdDsa,
1604 CryptographicKeyContext::OpenPgp {
1605 user_ids: OpenPgpUserIdList::new(vec![
1606 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1607 ])?,
1608 version: "v4".parse()?,
1609 },
1610 )?,
1611 "signing1",
1612 )),
1613 )]
1614 #[case::signing_system_wide_filter_namespaced(
1615 NetHsmUserMapping::Signing {
1616 backend_user: "ns1~signing".parse()?,
1617 signing_key_id: "signing1".parse()?,
1618 key_setup: SigningKeySetup::new(
1619 KeyType::Curve25519,
1620 vec![KeyMechanism::EdDsaSignature],
1621 None,
1622 SignatureType::EdDsa,
1623 CryptographicKeyContext::OpenPgp {
1624 user_ids: OpenPgpUserIdList::new(vec![
1625 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1626 ])?,
1627 version: "v4".parse()?,
1628 },
1629 )?,
1630 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1631 system_user: "signing-user".parse()?,
1632 tag: "signing1".to_string(),
1633 },
1634 NetHsmUserKeysFilter::Namespaced,
1635 Some((
1636 "ns1~signing".parse()?,
1637 "signing1".parse()?,
1638 SigningKeySetup::new(
1639 KeyType::Curve25519,
1640 vec![KeyMechanism::EdDsaSignature],
1641 None,
1642 SignatureType::EdDsa,
1643 CryptographicKeyContext::OpenPgp {
1644 user_ids: OpenPgpUserIdList::new(vec![
1645 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1646 ])?,
1647 version: "v4".parse()?,
1648 },
1649 )?,
1650 "signing1",
1651 )),
1652 )]
1653 #[case::signing_system_wide_filter_system_wide(
1654 NetHsmUserMapping::Signing {
1655 backend_user: "ns1~signing".parse()?,
1656 signing_key_id: "signing1".parse()?,
1657 key_setup: SigningKeySetup::new(
1658 KeyType::Curve25519,
1659 vec![KeyMechanism::EdDsaSignature],
1660 None,
1661 SignatureType::EdDsa,
1662 CryptographicKeyContext::OpenPgp {
1663 user_ids: OpenPgpUserIdList::new(vec![
1664 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1665 ])?,
1666 version: "v4".parse()?,
1667 },
1668 )?,
1669 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1670 system_user: "signing-user".parse()?,
1671 tag: "signing1".to_string(),
1672 },
1673 NetHsmUserKeysFilter::SystemWide,
1674 None,
1675 )]
1676 fn nethsm_user_mapping_nethsm_config_user_key_data(
1677 #[case] mapping: NetHsmUserMapping,
1678 #[case] filter: NetHsmUserKeysFilter,
1679 #[case] expected: Option<(UserId, KeyId, SigningKeySetup, &str)>,
1680 ) -> TestResult {
1681 let expected =
1682 expected
1683 .as_ref()
1684 .map(|(user, key_id, key_setup, tag)| NetHsmConfigUserKeyData {
1685 user,
1686 key_id,
1687 key_setup,
1688 tag,
1689 });
1690 assert_eq!(mapping.nethsm_config_user_key_data(filter), expected);
1691
1692 Ok(())
1693 }
1694
1695 #[rstest]
1696 #[case::admin_filter_admin(
1697 NetHsmUserMapping::Admin("admin".parse()?),
1698 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1699 vec!["admin"]
1700 )]
1701 #[case::admin_filter_any(
1702 NetHsmUserMapping::Admin("admin".parse()?),
1703 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1704 vec!["admin"]
1705 )]
1706 #[case::backup_filter_any(
1707 NetHsmUserMapping::Backup{
1708 backend_user: "backup".parse()?,
1709 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1710 system_user: "backup-user".parse()?,
1711 },
1712 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1713 vec!["backup"]
1714 )]
1715 #[case::backup_filter_backup(
1716 NetHsmUserMapping::Backup{
1717 backend_user: "backup".parse()?,
1718 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1719 system_user: "backup-user".parse()?,
1720 },
1721 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1722 vec!["backup"]
1723 )]
1724 #[case::backup_filter_non_admin(
1725 NetHsmUserMapping::Backup{
1726 backend_user: "backup".parse()?,
1727 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1728 system_user: "backup-user".parse()?,
1729 },
1730 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1731 vec!["backup"]
1732 )]
1733 #[case::hermetic_metrics_filter_any(
1734 NetHsmUserMapping::HermeticMetrics {
1735 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1736 system_user: "metrics-user".parse()?,
1737 },
1738 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1739 vec!["metrics", "keymetrics"]
1740 )]
1741 #[case::hermetic_metrics_filter_metrics(
1742 NetHsmUserMapping::HermeticMetrics {
1743 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1744 system_user: "metrics-user".parse()?,
1745 },
1746 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1747 vec!["metrics"]
1748 )]
1749 #[case::hermetic_metrics_filter_non_admin(
1750 NetHsmUserMapping::HermeticMetrics {
1751 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1752 system_user: "metrics-user".parse()?,
1753 },
1754 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1755 vec!["metrics", "keymetrics"]
1756 )]
1757 #[case::hermetic_metrics_filter_observer(
1758 NetHsmUserMapping::HermeticMetrics {
1759 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1760 system_user: "metrics-user".parse()?,
1761 },
1762 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1763 vec!["keymetrics"]
1764 )]
1765 #[case::signing_filter_any(
1766 NetHsmUserMapping::Signing {
1767 backend_user: "signing".parse()?,
1768 signing_key_id: "signing1".parse()?,
1769 key_setup: SigningKeySetup::new(
1770 KeyType::Curve25519,
1771 vec![KeyMechanism::EdDsaSignature],
1772 None,
1773 SignatureType::EdDsa,
1774 CryptographicKeyContext::OpenPgp {
1775 user_ids: OpenPgpUserIdList::new(vec![
1776 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1777 ])?,
1778 version: "v4".parse()?,
1779 },
1780 )?,
1781 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1782 system_user: "signing-user".parse()?,
1783 tag: "signing1".to_string(),
1784 },
1785 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
1786 vec!["signing"]
1787 )]
1788 #[case::signing_filter_non_admin(
1789 NetHsmUserMapping::Signing {
1790 backend_user: "signing".parse()?,
1791 signing_key_id: "signing1".parse()?,
1792 key_setup: SigningKeySetup::new(
1793 KeyType::Curve25519,
1794 vec![KeyMechanism::EdDsaSignature],
1795 None,
1796 SignatureType::EdDsa,
1797 CryptographicKeyContext::OpenPgp {
1798 user_ids: OpenPgpUserIdList::new(vec![
1799 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1800 ])?,
1801 version: "v4".parse()?,
1802 },
1803 )?,
1804 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1805 system_user: "signing-user".parse()?,
1806 tag: "signing1".to_string(),
1807 },
1808 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1809 vec!["signing"]
1810 )]
1811 #[case::signing_filter_signing(
1812 NetHsmUserMapping::Signing {
1813 backend_user: "signing".parse()?,
1814 signing_key_id: "signing1".parse()?,
1815 key_setup: SigningKeySetup::new(
1816 KeyType::Curve25519,
1817 vec![KeyMechanism::EdDsaSignature],
1818 None,
1819 SignatureType::EdDsa,
1820 CryptographicKeyContext::OpenPgp {
1821 user_ids: OpenPgpUserIdList::new(vec![
1822 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1823 ])?,
1824 version: "v4".parse()?,
1825 },
1826 )?,
1827 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1828 system_user: "signing-user".parse()?,
1829 tag: "signing1".to_string(),
1830 },
1831 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1832 vec!["signing"]
1833 )]
1834 fn nethsm_user_mapping_backend_user_ids_filter_matches(
1835 #[case] mapping: NetHsmUserMapping,
1836 #[case] filter: BackendUserIdFilter,
1837 #[case] expected: Vec<&str>,
1838 ) -> TestResult {
1839 assert_eq!(mapping.backend_user_ids(filter), expected);
1840
1841 Ok(())
1842 }
1843
1844 #[rstest]
1845 #[case::admin_filter_metrics(
1846 NetHsmUserMapping::Admin("admin".parse()?),
1847 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1848 )]
1849 #[case::admin_filter_non_admin(
1850 NetHsmUserMapping::Admin("admin".parse()?),
1851 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
1852 )]
1853 #[case::admin_filter_observer(
1854 NetHsmUserMapping::Admin("admin".parse()?),
1855 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1856 )]
1857 #[case::admin_filter_signing(
1858 NetHsmUserMapping::Admin("admin".parse()?),
1859 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1860 )]
1861 #[case::backup_filter_admin(
1862 NetHsmUserMapping::Backup{
1863 backend_user: "backup".parse()?,
1864 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1865 system_user: "backup-user".parse()?,
1866 },
1867 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1868 )]
1869 #[case::backup_filter_metrics(
1870 NetHsmUserMapping::Backup{
1871 backend_user: "backup".parse()?,
1872 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1873 system_user: "backup-user".parse()?,
1874 },
1875 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
1876 )]
1877 #[case::backup_filter_observer(
1878 NetHsmUserMapping::Backup{
1879 backend_user: "backup".parse()?,
1880 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1881 system_user: "backup-user".parse()?,
1882 },
1883 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
1884 )]
1885 #[case::backup_filter_signing(
1886 NetHsmUserMapping::Backup{
1887 backend_user: "backup".parse()?,
1888 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
1889 system_user: "backup-user".parse()?,
1890 },
1891 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1892 )]
1893 #[case::hermetic_metrics_filter_admin(
1894 NetHsmUserMapping::HermeticMetrics {
1895 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1896 system_user: "metrics-user".parse()?,
1897 },
1898 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1899 )]
1900 #[case::hermetic_metrics_filter_backup(
1901 NetHsmUserMapping::HermeticMetrics {
1902 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1903 system_user: "metrics-user".parse()?,
1904 },
1905 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1906 )]
1907 #[case::hermetic_metrics_filter_signing(
1908 NetHsmUserMapping::HermeticMetrics {
1909 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
1910 system_user: "metrics-user".parse()?,
1911 },
1912 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1913 )]
1914 #[case::metrics_filter_admin(
1915 NetHsmUserMapping::Metrics {
1916 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1917 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1918 system_user: "hermetic-metrics-user".parse()?,
1919 },
1920 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1921 )]
1922 #[case::metrics_filter_backup(
1923 NetHsmUserMapping::Metrics {
1924 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1925 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1926 system_user: "hermetic-metrics-user".parse()?,
1927 },
1928 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1929 )]
1930 #[case::metrics_filter_signing(
1931 NetHsmUserMapping::Metrics {
1932 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
1933 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
1934 system_user: "hermetic-metrics-user".parse()?,
1935 },
1936 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
1937 )]
1938 #[case::signing_filter_admin(
1939 NetHsmUserMapping::Signing {
1940 backend_user: "signing".parse()?,
1941 signing_key_id: "signing1".parse()?,
1942 key_setup: SigningKeySetup::new(
1943 KeyType::Curve25519,
1944 vec![KeyMechanism::EdDsaSignature],
1945 None,
1946 SignatureType::EdDsa,
1947 CryptographicKeyContext::OpenPgp {
1948 user_ids: OpenPgpUserIdList::new(vec![
1949 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1950 ])?,
1951 version: "v4".parse()?,
1952 },
1953 )?,
1954 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1955 system_user: "signing-user".parse()?,
1956 tag: "signing1".to_string(),
1957 },
1958 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
1959 )]
1960 #[case::signing_filter_backup(
1961 NetHsmUserMapping::Signing {
1962 backend_user: "signing".parse()?,
1963 signing_key_id: "signing1".parse()?,
1964 key_setup: SigningKeySetup::new(
1965 KeyType::Curve25519,
1966 vec![KeyMechanism::EdDsaSignature],
1967 None,
1968 SignatureType::EdDsa,
1969 CryptographicKeyContext::OpenPgp {
1970 user_ids: OpenPgpUserIdList::new(vec![
1971 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1972 ])?,
1973 version: "v4".parse()?,
1974 },
1975 )?,
1976 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1977 system_user: "signing-user".parse()?,
1978 tag: "signing1".to_string(),
1979 },
1980 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
1981 )]
1982 #[case::signing_filter_metrics(
1983 NetHsmUserMapping::Signing {
1984 backend_user: "signing".parse()?,
1985 signing_key_id: "signing1".parse()?,
1986 key_setup: SigningKeySetup::new(
1987 KeyType::Curve25519,
1988 vec![KeyMechanism::EdDsaSignature],
1989 None,
1990 SignatureType::EdDsa,
1991 CryptographicKeyContext::OpenPgp {
1992 user_ids: OpenPgpUserIdList::new(vec![
1993 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
1994 ])?,
1995 version: "v4".parse()?,
1996 },
1997 )?,
1998 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
1999 system_user: "signing-user".parse()?,
2000 tag: "signing1".to_string(),
2001 },
2002 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2003 )]
2004 #[case::signing_filter_observer(
2005 NetHsmUserMapping::Signing {
2006 backend_user: "signing".parse()?,
2007 signing_key_id: "signing1".parse()?,
2008 key_setup: SigningKeySetup::new(
2009 KeyType::Curve25519,
2010 vec![KeyMechanism::EdDsaSignature],
2011 None,
2012 SignatureType::EdDsa,
2013 CryptographicKeyContext::OpenPgp {
2014 user_ids: OpenPgpUserIdList::new(vec![
2015 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2016 ])?,
2017 version: "v4".parse()?,
2018 },
2019 )?,
2020 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2021 system_user: "signing-user".parse()?,
2022 tag: "signing1".to_string(),
2023 },
2024 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2025 )]
2026 fn nethsm_user_mapping_backend_user_ids_filter_mismatches(
2027 #[case] mapping: NetHsmUserMapping,
2028 #[case] filter: BackendUserIdFilter,
2029 ) -> TestResult {
2030 assert!(mapping.backend_user_ids(filter).is_empty());
2031
2032 Ok(())
2033 }
2034
2035 #[test]
2036 fn nethsm_user_mapping_backend_user_with_passphrase_succeeds() -> TestResult {
2037 let mapping = NetHsmUserMapping::Admin("admin".parse()?);
2038 let passphrase = Passphrase::generate(None);
2039 let creds = mapping.backend_user_with_passphrase("admin", passphrase.clone())?;
2040
2041 assert_eq!(creds.user(), "admin");
2042 assert_eq!(
2043 creds.passphrase().expose_borrowed(),
2044 passphrase.expose_borrowed()
2045 );
2046
2047 Ok(())
2048 }
2049
2050 #[rstest]
2051 #[case::admin_filter_admin(
2052 NetHsmUserMapping::Admin("admin".parse()?),
2053 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2054 vec!["admin"]
2055 )]
2056 #[case::admin_filter_any(
2057 NetHsmUserMapping::Admin("admin".parse()?),
2058 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2059 vec!["admin"]
2060 )]
2061 #[case::backup_filter_any(
2062 NetHsmUserMapping::Backup{
2063 backend_user: "backup".parse()?,
2064 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2065 system_user: "backup-user".parse()?,
2066 },
2067 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2068 vec!["backup"]
2069 )]
2070 #[case::backup_filter_backup(
2071 NetHsmUserMapping::Backup{
2072 backend_user: "backup".parse()?,
2073 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2074 system_user: "backup-user".parse()?,
2075 },
2076 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2077 vec!["backup"]
2078 )]
2079 #[case::backup_filter_non_admin(
2080 NetHsmUserMapping::Backup{
2081 backend_user: "backup".parse()?,
2082 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2083 system_user: "backup-user".parse()?,
2084 },
2085 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2086 vec!["backup"]
2087 )]
2088 #[case::hermetic_metrics_filter_any(
2089 NetHsmUserMapping::HermeticMetrics {
2090 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2091 system_user: "metrics-user".parse()?,
2092 },
2093 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2094 vec!["metrics", "keymetrics"]
2095 )]
2096 #[case::hermetic_metrics_filter_metrics(
2097 NetHsmUserMapping::HermeticMetrics {
2098 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2099 system_user: "metrics-user".parse()?,
2100 },
2101 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2102 vec!["metrics"]
2103 )]
2104 #[case::hermetic_metrics_filter_non_admin(
2105 NetHsmUserMapping::HermeticMetrics {
2106 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2107 system_user: "metrics-user".parse()?,
2108 },
2109 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2110 vec!["metrics", "keymetrics"]
2111 )]
2112 #[case::hermetic_metrics_filter_observer(
2113 NetHsmUserMapping::HermeticMetrics {
2114 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2115 system_user: "metrics-user".parse()?,
2116 },
2117 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2118 vec!["keymetrics"]
2119 )]
2120 #[case::signing_filter_any(
2121 NetHsmUserMapping::Signing {
2122 backend_user: "signing".parse()?,
2123 signing_key_id: "signing1".parse()?,
2124 key_setup: SigningKeySetup::new(
2125 KeyType::Curve25519,
2126 vec![KeyMechanism::EdDsaSignature],
2127 None,
2128 SignatureType::EdDsa,
2129 CryptographicKeyContext::OpenPgp {
2130 user_ids: OpenPgpUserIdList::new(vec![
2131 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2132 ])?,
2133 version: "v4".parse()?,
2134 },
2135 )?,
2136 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2137 system_user: "signing-user".parse()?,
2138 tag: "signing1".to_string(),
2139 },
2140 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Any },
2141 vec!["signing"]
2142 )]
2143 #[case::signing_filter_non_admin(
2144 NetHsmUserMapping::Signing {
2145 backend_user: "signing".parse()?,
2146 signing_key_id: "signing1".parse()?,
2147 key_setup: SigningKeySetup::new(
2148 KeyType::Curve25519,
2149 vec![KeyMechanism::EdDsaSignature],
2150 None,
2151 SignatureType::EdDsa,
2152 CryptographicKeyContext::OpenPgp {
2153 user_ids: OpenPgpUserIdList::new(vec![
2154 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2155 ])?,
2156 version: "v4".parse()?,
2157 },
2158 )?,
2159 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2160 system_user: "signing-user".parse()?,
2161 tag: "signing1".to_string(),
2162 },
2163 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2164 vec!["signing"]
2165 )]
2166 #[case::signing_filter_signing(
2167 NetHsmUserMapping::Signing {
2168 backend_user: "signing".parse()?,
2169 signing_key_id: "signing1".parse()?,
2170 key_setup: SigningKeySetup::new(
2171 KeyType::Curve25519,
2172 vec![KeyMechanism::EdDsaSignature],
2173 None,
2174 SignatureType::EdDsa,
2175 CryptographicKeyContext::OpenPgp {
2176 user_ids: OpenPgpUserIdList::new(vec![
2177 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2178 ])?,
2179 version: "v4".parse()?,
2180 },
2181 )?,
2182 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2183 system_user: "signing-user".parse()?,
2184 tag: "signing1".to_string(),
2185 },
2186 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2187 vec!["signing"]
2188 )]
2189
2190 fn nethsm_user_mapping_backend_users_with_new_passphrase_filter_matches(
2191 #[case] mapping: NetHsmUserMapping,
2192 #[case] filter: BackendUserIdFilter,
2193 #[case] expected: Vec<&str>,
2194 ) -> TestResult {
2195 let creds = mapping.backend_users_with_new_passphrase(filter);
2196 let users = creds
2197 .iter()
2198 .map(|creds| creds.user())
2199 .collect::<HashSet<_>>();
2200 let expected = expected
2201 .iter()
2202 .map(ToString::to_string)
2203 .collect::<HashSet<_>>();
2204 assert_eq!(users, expected);
2205
2206 Ok(())
2207 }
2208
2209 #[rstest]
2210 #[case::admin_filter_metrics(
2211 NetHsmUserMapping::Admin("admin".parse()?),
2212 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2213 )]
2214 #[case::admin_filter_non_admin(
2215 NetHsmUserMapping::Admin("admin".parse()?),
2216 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::NonAdmin },
2217 )]
2218 #[case::admin_filter_observer(
2219 NetHsmUserMapping::Admin("admin".parse()?),
2220 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2221 )]
2222 #[case::admin_filter_signing(
2223 NetHsmUserMapping::Admin("admin".parse()?),
2224 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2225 )]
2226 #[case::backup_filter_admin(
2227 NetHsmUserMapping::Backup{
2228 backend_user: "backup".parse()?,
2229 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2230 system_user: "backup-user".parse()?,
2231 },
2232 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2233 )]
2234 #[case::backup_filter_metrics(
2235 NetHsmUserMapping::Backup{
2236 backend_user: "backup".parse()?,
2237 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2238 system_user: "backup-user".parse()?,
2239 },
2240 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2241 )]
2242 #[case::backup_filter_observer(
2243 NetHsmUserMapping::Backup{
2244 backend_user: "backup".parse()?,
2245 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2246 system_user: "backup-user".parse()?,
2247 },
2248 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2249 )]
2250 #[case::backup_filter_signing(
2251 NetHsmUserMapping::Backup{
2252 backend_user: "backup".parse()?,
2253 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2254 system_user: "backup-user".parse()?,
2255 },
2256 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2257 )]
2258 #[case::hermetic_metrics_filter_admin(
2259 NetHsmUserMapping::HermeticMetrics {
2260 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2261 system_user: "metrics-user".parse()?,
2262 },
2263 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2264 )]
2265 #[case::hermetic_metrics_filter_backup(
2266 NetHsmUserMapping::HermeticMetrics {
2267 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2268 system_user: "metrics-user".parse()?,
2269 },
2270 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2271 )]
2272 #[case::hermetic_metrics_filter_signing(
2273 NetHsmUserMapping::HermeticMetrics {
2274 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2275 system_user: "metrics-user".parse()?,
2276 },
2277 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2278 )]
2279 #[case::metrics_filter_admin(
2280 NetHsmUserMapping::Metrics {
2281 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2282 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2283 system_user: "hermetic-metrics-user".parse()?,
2284 },
2285 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2286 )]
2287 #[case::metrics_filter_backup(
2288 NetHsmUserMapping::Metrics {
2289 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2290 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2291 system_user: "hermetic-metrics-user".parse()?,
2292 },
2293 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2294 )]
2295 #[case::metrics_filter_signing(
2296 NetHsmUserMapping::Metrics {
2297 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2298 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2299 system_user: "hermetic-metrics-user".parse()?,
2300 },
2301 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Signing },
2302 )]
2303 #[case::signing_filter_admin(
2304 NetHsmUserMapping::Signing {
2305 backend_user: "signing".parse()?,
2306 signing_key_id: "signing1".parse()?,
2307 key_setup: SigningKeySetup::new(
2308 KeyType::Curve25519,
2309 vec![KeyMechanism::EdDsaSignature],
2310 None,
2311 SignatureType::EdDsa,
2312 CryptographicKeyContext::OpenPgp {
2313 user_ids: OpenPgpUserIdList::new(vec![
2314 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2315 ])?,
2316 version: "v4".parse()?,
2317 },
2318 )?,
2319 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2320 system_user: "signing-user".parse()?,
2321 tag: "signing1".to_string(),
2322 },
2323 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Admin },
2324 )]
2325 #[case::signing_filter_backup(
2326 NetHsmUserMapping::Signing {
2327 backend_user: "signing".parse()?,
2328 signing_key_id: "signing1".parse()?,
2329 key_setup: SigningKeySetup::new(
2330 KeyType::Curve25519,
2331 vec![KeyMechanism::EdDsaSignature],
2332 None,
2333 SignatureType::EdDsa,
2334 CryptographicKeyContext::OpenPgp {
2335 user_ids: OpenPgpUserIdList::new(vec![
2336 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2337 ])?,
2338 version: "v4".parse()?,
2339 },
2340 )?,
2341 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2342 system_user: "signing-user".parse()?,
2343 tag: "signing1".to_string(),
2344 },
2345 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Backup },
2346 )]
2347 #[case::signing_filter_metrics(
2348 NetHsmUserMapping::Signing {
2349 backend_user: "signing".parse()?,
2350 signing_key_id: "signing1".parse()?,
2351 key_setup: SigningKeySetup::new(
2352 KeyType::Curve25519,
2353 vec![KeyMechanism::EdDsaSignature],
2354 None,
2355 SignatureType::EdDsa,
2356 CryptographicKeyContext::OpenPgp {
2357 user_ids: OpenPgpUserIdList::new(vec![
2358 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2359 ])?,
2360 version: "v4".parse()?,
2361 },
2362 )?,
2363 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2364 system_user: "signing-user".parse()?,
2365 tag: "signing1".to_string(),
2366 },
2367 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Metrics },
2368 )]
2369 #[case::signing_filter_observer(
2370 NetHsmUserMapping::Signing {
2371 backend_user: "signing".parse()?,
2372 signing_key_id: "signing1".parse()?,
2373 key_setup: SigningKeySetup::new(
2374 KeyType::Curve25519,
2375 vec![KeyMechanism::EdDsaSignature],
2376 None,
2377 SignatureType::EdDsa,
2378 CryptographicKeyContext::OpenPgp {
2379 user_ids: OpenPgpUserIdList::new(vec![
2380 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2381 ])?,
2382 version: "v4".parse()?,
2383 },
2384 )?,
2385 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2386 system_user: "signing-user".parse()?,
2387 tag: "signing1".to_string(),
2388 },
2389 BackendUserIdFilter{ backend_user_id_kind: BackendUserIdKind::Observer },
2390 )]
2391 fn nethsm_user_mapping_backend_users_with_new_passphrase_filter_mismatches(
2392 #[case] mapping: NetHsmUserMapping,
2393 #[case] filter: BackendUserIdFilter,
2394 ) -> TestResult {
2395 assert!(mapping.backend_users_with_new_passphrase(filter).is_empty());
2396
2397 Ok(())
2398 }
2399
2400 #[test]
2401 fn nethsm_user_mapping_backend_user_with_passphrase_fails() -> TestResult {
2402 let mapping = NetHsmUserMapping::Admin("admin".parse()?);
2403 assert!(
2404 mapping
2405 .backend_user_with_passphrase("2", Passphrase::generate(None))
2406 .is_err()
2407 );
2408
2409 Ok(())
2410 }
2411
2412 #[fixture]
2413 fn nethsm_config_connections() -> TestResult<BTreeSet<Connection>> {
2414 Ok(BTreeSet::from_iter([
2415 Connection::new(
2416 "https://nethsm1.example.org/".parse()?,
2417 nethsm::ConnectionSecurity::Unsafe,
2418 ),
2419 Connection::new(
2420 "https://nethsm2.example.org/".parse()?,
2421 nethsm::ConnectionSecurity::Unsafe,
2422 ),
2423 ]))
2424 }
2425
2426 #[fixture]
2427 fn nethsm_config_mappings() -> TestResult<BTreeSet<NetHsmUserMapping>> {
2428 Ok(BTreeSet::from_iter([
2429 NetHsmUserMapping::Admin("admin".parse()?),
2430 NetHsmUserMapping::Backup{
2431 backend_user: "backup".parse()?,
2432 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2433 system_user: "backup-user".parse()?,
2434 },
2435 NetHsmUserMapping::HermeticMetrics {
2436 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2437 system_user: "metrics-user".parse()?,
2438 },
2439 NetHsmUserMapping::Metrics {
2440 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2441 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2442 system_user: "hermetic-metrics-user".parse()?,
2443 },
2444 NetHsmUserMapping::Signing {
2445 backend_user: "signing".parse()?,
2446 signing_key_id: "signing1".parse()?,
2447 key_setup: SigningKeySetup::new(
2448 KeyType::Curve25519,
2449 vec![KeyMechanism::EdDsaSignature],
2450 None,
2451 SignatureType::EdDsa,
2452 CryptographicKeyContext::OpenPgp {
2453 user_ids: OpenPgpUserIdList::new(vec![
2454 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2455 ])?,
2456 version: "v4".parse()?,
2457 },
2458 )?,
2459 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2460 system_user: "signing-user".parse()?,
2461 tag: "signing1".to_string(),
2462 }
2463 ]))
2464 }
2465
2466 #[fixture]
2467 fn nethsm_config(
2468 nethsm_config_connections: TestResult<BTreeSet<Connection>>,
2469 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2470 ) -> TestResult<NetHsmConfig> {
2471 let nethsm_config_connections = nethsm_config_connections?;
2472 let nethsm_config_mappings = nethsm_config_mappings?;
2473 Ok(NetHsmConfig::new(
2474 nethsm_config_connections,
2475 nethsm_config_mappings,
2476 )?)
2477 }
2478
2479 #[rstest]
2480 fn nethsm_config_connections_matches(
2481 nethsm_config: TestResult<NetHsmConfig>,
2482 nethsm_config_connections: TestResult<BTreeSet<Connection>>,
2483 ) -> TestResult {
2484 let nethsm_config = nethsm_config?;
2485 let nethsm_config_connections = nethsm_config_connections?;
2486 assert_eq!(nethsm_config.connections(), &nethsm_config_connections);
2487
2488 Ok(())
2489 }
2490
2491 #[rstest]
2492 fn nethsm_config_mappings_matches(
2493 nethsm_config: TestResult<NetHsmConfig>,
2494 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2495 ) -> TestResult {
2496 let nethsm_config = nethsm_config?;
2497 let nethsm_config_mappings = nethsm_config_mappings?;
2498 assert_eq!(nethsm_config.mappings(), &nethsm_config_mappings);
2499
2500 Ok(())
2501 }
2502
2503 #[rstest]
2504 fn nethsm_config_authorized_key_entries(
2505 nethsm_config: TestResult<NetHsmConfig>,
2506 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2507 ) -> TestResult {
2508 let nethsm_config = nethsm_config?;
2509 let nethsm_config_mappings = nethsm_config_mappings?;
2510 let expected = nethsm_config_mappings
2511 .iter()
2512 .filter_map(|mapping| mapping.authorized_key_entry())
2513 .collect::<HashSet<_>>();
2514 assert_eq!(nethsm_config.authorized_key_entries(), expected);
2515
2516 Ok(())
2517 }
2518
2519 #[rstest]
2520 fn nethsm_config_system_user_ids(
2521 nethsm_config: TestResult<NetHsmConfig>,
2522 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
2523 ) -> TestResult {
2524 let nethsm_config = nethsm_config?;
2525 let nethsm_config_mappings = nethsm_config_mappings?;
2526 let expected = nethsm_config_mappings
2527 .iter()
2528 .filter_map(|mapping| mapping.system_user_id())
2529 .collect::<HashSet<_>>();
2530 assert_eq!(nethsm_config.system_user_ids(), expected);
2531
2532 Ok(())
2533 }
2534
2535 #[rstest]
2536 #[case::no_connection(
2537 "Error message for NetHsmConfig::new with no backend connection",
2538 BTreeSet::new(),
2539 BTreeSet::from_iter([
2540 NetHsmUserMapping::Admin("admin".parse()?),
2541 NetHsmUserMapping::Backup{
2542 backend_user: "backup".parse()?,
2543 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2544 system_user: "backup-user".parse()?,
2545 },
2546 NetHsmUserMapping::HermeticMetrics {
2547 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2548 system_user: "hermetic-metrics-user".parse()?,
2549 },
2550 NetHsmUserMapping::Metrics {
2551 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2552 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2553 system_user: "metrics-user".parse()?,
2554 },
2555 NetHsmUserMapping::Signing {
2556 backend_user: "signing".parse()?,
2557 signing_key_id: "signing1".parse()?,
2558 key_setup: SigningKeySetup::new(
2559 KeyType::Curve25519,
2560 vec![KeyMechanism::EdDsaSignature],
2561 None,
2562 SignatureType::EdDsa,
2563 CryptographicKeyContext::OpenPgp {
2564 user_ids: OpenPgpUserIdList::new(vec![
2565 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2566 ])?,
2567 version: "v4".parse()?,
2568 },
2569 )?,
2570 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2571 system_user: "signing-user".parse()?,
2572 tag: "signing1".to_string(),
2573 }
2574 ]),
2575 )]
2576 #[case::duplicate_connection_url(
2577 "Error message for NetHsmConfig::new with two duplicate connection URLs",
2578 BTreeSet::from_iter([
2579 Connection::new("https://nethsm1.example.org/".parse()?, nethsm::ConnectionSecurity::Unsafe),
2580 Connection::new("https://nethsm1.example.org/".parse()?, nethsm::ConnectionSecurity::Native),
2581 ]),
2582 BTreeSet::from_iter([
2583 NetHsmUserMapping::Admin("admin".parse()?),
2584 NetHsmUserMapping::Backup{
2585 backend_user: "backup".parse()?,
2586 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2587 system_user: "backup-user".parse()?,
2588 },
2589 NetHsmUserMapping::HermeticMetrics {
2590 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2591 system_user: "hermetic-metrics-user".parse()?,
2592 },
2593 NetHsmUserMapping::Metrics {
2594 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2595 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2596 system_user: "metrics-user".parse()?,
2597 },
2598 NetHsmUserMapping::Signing {
2599 backend_user: "signing".parse()?,
2600 signing_key_id: "signing1".parse()?,
2601 key_setup: SigningKeySetup::new(
2602 KeyType::Curve25519,
2603 vec![KeyMechanism::EdDsaSignature],
2604 None,
2605 SignatureType::EdDsa,
2606 CryptographicKeyContext::OpenPgp {
2607 user_ids: OpenPgpUserIdList::new(vec![
2608 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2609 ])?,
2610 version: "v4".parse()?,
2611 },
2612 )?,
2613 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2614 system_user: "signing-user".parse()?,
2615 tag: "signing1".to_string(),
2616 }
2617 ]),
2618 )]
2619 #[case::no_mappings(
2620 "Error message for NetHsmConfig::new with no user mappings",
2621 BTreeSet::from_iter([
2622 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2623 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2624 ]),
2625 BTreeSet::new(),
2626 )]
2627 #[case::duplicate_system_user_ids(
2628 "Error message for NetHsmConfig::new with two duplicate system user IDs",
2629 BTreeSet::from_iter([
2630 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2631 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2632 ]),
2633 BTreeSet::from_iter([
2634 NetHsmUserMapping::Admin("admin".parse()?),
2635 NetHsmUserMapping::Backup{
2636 backend_user: "backup".parse()?,
2637 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2638 system_user: "backup-user".parse()?,
2639 },
2640 NetHsmUserMapping::HermeticMetrics {
2641 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2642 system_user: "hermetic-metrics-user".parse()?,
2643 },
2644 NetHsmUserMapping::Metrics {
2645 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2646 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2647 system_user: "backup-user".parse()?,
2648 },
2649 NetHsmUserMapping::Signing {
2650 backend_user: "signing".parse()?,
2651 signing_key_id: "signing1".parse()?,
2652 key_setup: SigningKeySetup::new(
2653 KeyType::Curve25519,
2654 vec![KeyMechanism::EdDsaSignature],
2655 None,
2656 SignatureType::EdDsa,
2657 CryptographicKeyContext::OpenPgp {
2658 user_ids: OpenPgpUserIdList::new(vec![
2659 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2660 ])?,
2661 version: "v4".parse()?,
2662 },
2663 )?,
2664 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2665 system_user: "signing-user".parse()?,
2666 tag: "signing1".to_string(),
2667 }
2668 ]),
2669 )]
2670 #[case::duplicate_ssh_public_keys(
2671 "Error message for NetHsmConfig::new with two duplicate SSH public keys as authorized keys",
2672 BTreeSet::from_iter([
2673 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2674 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2675 ]),
2676 BTreeSet::from_iter([
2677 NetHsmUserMapping::Admin("admin".parse()?),
2678 NetHsmUserMapping::Backup{
2679 backend_user: "backup".parse()?,
2680 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2681 system_user: "backup-user".parse()?,
2682 },
2683 NetHsmUserMapping::HermeticMetrics {
2684 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2685 system_user: "hermetic-metrics-user".parse()?,
2686 },
2687 NetHsmUserMapping::Metrics {
2688 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2689 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user2@other-host".parse()?,
2690 system_user: "metrics-user".parse()?,
2691 },
2692 NetHsmUserMapping::Signing {
2693 backend_user: "signing".parse()?,
2694 signing_key_id: "signing1".parse()?,
2695 key_setup: SigningKeySetup::new(
2696 KeyType::Curve25519,
2697 vec![KeyMechanism::EdDsaSignature],
2698 None,
2699 SignatureType::EdDsa,
2700 CryptographicKeyContext::OpenPgp {
2701 user_ids: OpenPgpUserIdList::new(vec![
2702 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2703 ])?,
2704 version: "v4".parse()?,
2705 },
2706 )?,
2707 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2708 system_user: "signing-user".parse()?,
2709 tag: "signing1".to_string(),
2710 }
2711 ]),
2712 )]
2713 #[case::missing_system_wide_administrator(
2714 "Error message for NetHsmConfig::new with a system-wide administrator missing",
2715 BTreeSet::from_iter([
2716 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2717 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2718 ]),
2719 BTreeSet::from_iter([
2720 NetHsmUserMapping::Backup{
2721 backend_user: "backup".parse()?,
2722 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2723 system_user: "backup-user".parse()?,
2724 },
2725 NetHsmUserMapping::HermeticMetrics {
2726 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2727 system_user: "hermetic-metrics-user".parse()?,
2728 },
2729 NetHsmUserMapping::Metrics {
2730 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2731 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2732 system_user: "metrics-user".parse()?,
2733 },
2734 NetHsmUserMapping::Signing {
2735 backend_user: "signing".parse()?,
2736 signing_key_id: "signing1".parse()?,
2737 key_setup: SigningKeySetup::new(
2738 KeyType::Curve25519,
2739 vec![KeyMechanism::EdDsaSignature],
2740 None,
2741 SignatureType::EdDsa,
2742 CryptographicKeyContext::OpenPgp {
2743 user_ids: OpenPgpUserIdList::new(vec![
2744 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2745 ])?,
2746 version: "v4".parse()?,
2747 },
2748 )?,
2749 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2750 system_user: "signing-user".parse()?,
2751 tag: "signing1".to_string(),
2752 }
2753 ]),
2754 )]
2755 #[case::duplicate_system_wide_backend_user_ids(
2756 "Error message for NetHsmConfig::new with two duplicate system-wide backend user IDs",
2757 BTreeSet::from_iter([
2758 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2759 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2760 ]),
2761 BTreeSet::from_iter([
2762 NetHsmUserMapping::Admin("admin".parse()?),
2763 NetHsmUserMapping::Admin("backup".parse()?),
2764 NetHsmUserMapping::Backup{
2765 backend_user: "backup".parse()?,
2766 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2767 system_user: "backup-user".parse()?,
2768 },
2769 NetHsmUserMapping::HermeticMetrics {
2770 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2771 system_user: "hermetic-metrics-user".parse()?,
2772 },
2773 NetHsmUserMapping::Metrics {
2774 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2775 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2776 system_user: "metrics-user".parse()?,
2777 },
2778 NetHsmUserMapping::Signing {
2779 backend_user: "signing".parse()?,
2780 signing_key_id: "signing1".parse()?,
2781 key_setup: SigningKeySetup::new(
2782 KeyType::Curve25519,
2783 vec![KeyMechanism::EdDsaSignature],
2784 None,
2785 SignatureType::EdDsa,
2786 CryptographicKeyContext::OpenPgp {
2787 user_ids: OpenPgpUserIdList::new(vec![
2788 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2789 ])?,
2790 version: "v4".parse()?,
2791 },
2792 )?,
2793 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2794 system_user: "signing-user".parse()?,
2795 tag: "signing1".to_string(),
2796 }
2797 ]),
2798 )]
2799 #[case::duplicate_namespaced_backend_user_ids(
2800 "Error message for NetHsmConfig::new with two duplicate namespaced backend user IDs",
2801 BTreeSet::from_iter([
2802 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2803 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2804 ]),
2805 BTreeSet::from_iter([
2806 NetHsmUserMapping::Admin("admin".parse()?),
2807 NetHsmUserMapping::Admin("ns1~admin".parse()?),
2808 NetHsmUserMapping::Signing {
2809 backend_user: "ns1~signing1".parse()?,
2810 signing_key_id: "signing1".parse()?,
2811 key_setup: SigningKeySetup::new(
2812 KeyType::Curve25519,
2813 vec![KeyMechanism::EdDsaSignature],
2814 None,
2815 SignatureType::EdDsa,
2816 CryptographicKeyContext::OpenPgp {
2817 user_ids: OpenPgpUserIdList::new(vec![
2818 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2819 ])?,
2820 version: "v4".parse()?,
2821 },
2822 )?,
2823 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2824 system_user: "ns1-signing-user1".parse()?,
2825 tag: "signing1".to_string(),
2826 },
2827 NetHsmUserMapping::Signing {
2828 backend_user: "ns1~signing1".parse()?,
2829 signing_key_id: "signing2".parse()?,
2830 key_setup: SigningKeySetup::new(
2831 KeyType::Curve25519,
2832 vec![KeyMechanism::EdDsaSignature],
2833 None,
2834 SignatureType::EdDsa,
2835 CryptographicKeyContext::OpenPgp {
2836 user_ids: OpenPgpUserIdList::new(vec![
2837 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2838 ])?,
2839 version: "v4".parse()?,
2840 },
2841 )?,
2842 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2843 system_user: "ns1-signing-user2".parse()?,
2844 tag: "signing2".to_string(),
2845 }
2846 ]),
2847 )]
2848 #[case::duplicate_system_wide_key_ids(
2849 "Error message for NetHsmConfig::new with two duplicate system-wide key IDs",
2850 BTreeSet::from_iter([
2851 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2852 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2853 ]),
2854 BTreeSet::from_iter([
2855 NetHsmUserMapping::Admin("admin".parse()?),
2856 NetHsmUserMapping::Backup{
2857 backend_user: "backup".parse()?,
2858 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2859 system_user: "backup-user".parse()?,
2860 },
2861 NetHsmUserMapping::HermeticMetrics {
2862 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2863 system_user: "hermetic-metrics-user".parse()?,
2864 },
2865 NetHsmUserMapping::Metrics {
2866 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2867 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2868 system_user: "metrics-user".parse()?,
2869 },
2870 NetHsmUserMapping::Signing {
2871 backend_user: "signing1".parse()?,
2872 signing_key_id: "signing1".parse()?,
2873 key_setup: SigningKeySetup::new(
2874 KeyType::Curve25519,
2875 vec![KeyMechanism::EdDsaSignature],
2876 None,
2877 SignatureType::EdDsa,
2878 CryptographicKeyContext::OpenPgp {
2879 user_ids: OpenPgpUserIdList::new(vec![
2880 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2881 ])?,
2882 version: "v4".parse()?,
2883 },
2884 )?,
2885 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2886 system_user: "signing-user".parse()?,
2887 tag: "signing1".to_string(),
2888 },
2889 NetHsmUserMapping::Signing {
2890 backend_user: "signing2".parse()?,
2891 signing_key_id: "signing1".parse()?,
2892 key_setup: SigningKeySetup::new(
2893 KeyType::Curve25519,
2894 vec![KeyMechanism::EdDsaSignature],
2895 None,
2896 SignatureType::EdDsa,
2897 CryptographicKeyContext::OpenPgp {
2898 user_ids: OpenPgpUserIdList::new(vec![
2899 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2900 ])?,
2901 version: "v4".parse()?,
2902 },
2903 )?,
2904 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2905 system_user: "signing-user2".parse()?,
2906 tag: "signing2".to_string(),
2907 }
2908 ]),
2909 )]
2910 #[case::duplicate_system_wide_tags(
2911 "Error message for NetHsmConfig::new with two duplicate system-wide tags",
2912 BTreeSet::from_iter([
2913 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2914 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2915 ]),
2916 BTreeSet::from_iter([
2917 NetHsmUserMapping::Admin("admin".parse()?),
2918 NetHsmUserMapping::Backup{
2919 backend_user: "backup".parse()?,
2920 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh9BTe81DC6A0YZALsq9dWcyl6xjjqlxWPwlExTFgBt user@host".parse()?,
2921 system_user: "backup-user".parse()?,
2922 },
2923 NetHsmUserMapping::HermeticMetrics {
2924 backend_users: NetHsmMetricsUsers::new("hermeticmetrics".parse()?, vec!["hermetickeymetrics".parse()?])?,
2925 system_user: "hermetic-metrics-user".parse()?,
2926 },
2927 NetHsmUserMapping::Metrics {
2928 backend_users: NetHsmMetricsUsers::new("metrics".parse()?, vec!["keymetrics".parse()?])?,
2929 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPkpXKiNhy39A3bZ1u19a5d4sFwYMBkWQyCbzgUfdKBm user@host".parse()?,
2930 system_user: "metrics-user".parse()?,
2931 },
2932 NetHsmUserMapping::Signing {
2933 backend_user: "signing1".parse()?,
2934 signing_key_id: "signing1".parse()?,
2935 key_setup: SigningKeySetup::new(
2936 KeyType::Curve25519,
2937 vec![KeyMechanism::EdDsaSignature],
2938 None,
2939 SignatureType::EdDsa,
2940 CryptographicKeyContext::OpenPgp {
2941 user_ids: OpenPgpUserIdList::new(vec![
2942 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2943 ])?,
2944 version: "v4".parse()?,
2945 },
2946 )?,
2947 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2948 system_user: "signing-user".parse()?,
2949 tag: "signing1".to_string(),
2950 },
2951 NetHsmUserMapping::Signing {
2952 backend_user: "signing2".parse()?,
2953 signing_key_id: "signing2".parse()?,
2954 key_setup: SigningKeySetup::new(
2955 KeyType::Curve25519,
2956 vec![KeyMechanism::EdDsaSignature],
2957 None,
2958 SignatureType::EdDsa,
2959 CryptographicKeyContext::OpenPgp {
2960 user_ids: OpenPgpUserIdList::new(vec![
2961 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2962 ])?,
2963 version: "v4".parse()?,
2964 },
2965 )?,
2966 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
2967 system_user: "signing-user2".parse()?,
2968 tag: "signing1".to_string(),
2969 }
2970 ]),
2971 )]
2972 #[case::missing_namespace_administrator(
2973 "Error message for NetHsmConfig::new with a missing namespace administrator",
2974 BTreeSet::from_iter([
2975 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2976 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
2977 ]),
2978 BTreeSet::from_iter([
2979 NetHsmUserMapping::Admin("admin".parse()?),
2980 NetHsmUserMapping::Signing {
2981 backend_user: "ns1~signing1".parse()?,
2982 signing_key_id: "signing1".parse()?,
2983 key_setup: SigningKeySetup::new(
2984 KeyType::Curve25519,
2985 vec![KeyMechanism::EdDsaSignature],
2986 None,
2987 SignatureType::EdDsa,
2988 CryptographicKeyContext::OpenPgp {
2989 user_ids: OpenPgpUserIdList::new(vec![
2990 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
2991 ])?,
2992 version: "v4".parse()?,
2993 },
2994 )?,
2995 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
2996 system_user: "ns1-signing-user".parse()?,
2997 tag: "signing1".to_string(),
2998 },
2999 ]),
3000 )]
3001 #[case::duplicate_namespace_key_ids(
3002 "Error message for NetHsmConfig::new with two duplicate namespaced key IDs",
3003 BTreeSet::from_iter([
3004 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3005 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3006 ]),
3007 BTreeSet::from_iter([
3008 NetHsmUserMapping::Admin("admin".parse()?),
3009 NetHsmUserMapping::Admin("ns1~admin".parse()?),
3010 NetHsmUserMapping::Signing {
3011 backend_user: "ns1~signing1".parse()?,
3012 signing_key_id: "signing1".parse()?,
3013 key_setup: SigningKeySetup::new(
3014 KeyType::Curve25519,
3015 vec![KeyMechanism::EdDsaSignature],
3016 None,
3017 SignatureType::EdDsa,
3018 CryptographicKeyContext::OpenPgp {
3019 user_ids: OpenPgpUserIdList::new(vec![
3020 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3021 ])?,
3022 version: "v4".parse()?,
3023 },
3024 )?,
3025 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3026 system_user: "ns1-signing-user".parse()?,
3027 tag: "signing1".to_string(),
3028 },
3029 NetHsmUserMapping::Signing {
3030 backend_user: "ns1~signing2".parse()?,
3031 signing_key_id: "signing1".parse()?,
3032 key_setup: SigningKeySetup::new(
3033 KeyType::Curve25519,
3034 vec![KeyMechanism::EdDsaSignature],
3035 None,
3036 SignatureType::EdDsa,
3037 CryptographicKeyContext::OpenPgp {
3038 user_ids: OpenPgpUserIdList::new(vec![
3039 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3040 ])?,
3041 version: "v4".parse()?,
3042 },
3043 )?,
3044 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3045 system_user: "ns1-signing-user2".parse()?,
3046 tag: "signing2".to_string(),
3047 }
3048 ]),
3049 )]
3050 #[case::duplicate_namespace_tags(
3051 "Error message for NetHsmConfig::new with two duplicate namespaced tags",
3052 BTreeSet::from_iter([
3053 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3054 Connection::new("https://nethsm2.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3055 ]),
3056 BTreeSet::from_iter([
3057 NetHsmUserMapping::Admin("admin".parse()?),
3058 NetHsmUserMapping::Admin("ns1~admin".parse()?),
3059 NetHsmUserMapping::Signing {
3060 backend_user: "ns1~signing1".parse()?,
3061 signing_key_id: "signing1".parse()?,
3062 key_setup: SigningKeySetup::new(
3063 KeyType::Curve25519,
3064 vec![KeyMechanism::EdDsaSignature],
3065 None,
3066 SignatureType::EdDsa,
3067 CryptographicKeyContext::OpenPgp {
3068 user_ids: OpenPgpUserIdList::new(vec![
3069 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3070 ])?,
3071 version: "v4".parse()?,
3072 },
3073 )?,
3074 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3075 system_user: "ns1-signing-user".parse()?,
3076 tag: "signing1".to_string(),
3077 },
3078 NetHsmUserMapping::Signing {
3079 backend_user: "ns1~signing2".parse()?,
3080 signing_key_id: "signing2".parse()?,
3081 key_setup: SigningKeySetup::new(
3082 KeyType::Curve25519,
3083 vec![KeyMechanism::EdDsaSignature],
3084 None,
3085 SignatureType::EdDsa,
3086 CryptographicKeyContext::OpenPgp {
3087 user_ids: OpenPgpUserIdList::new(vec![
3088 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3089 ])?,
3090 version: "v4".parse()?,
3091 },
3092 )?,
3093 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3094 system_user: "ns1-signing-user2".parse()?,
3095 tag: "signing1".to_string(),
3096 }
3097 ]),
3098 )]
3099 #[case::all_the_issues(
3100 "Error message for NetHsmConfig::new with multiple validation issues (connections and mappings)",
3101 BTreeSet::from_iter([
3102 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Unsafe),
3103 Connection::new("https://nethsm1.example.org/".parse()?,nethsm::ConnectionSecurity::Native),
3104 ]),
3105 BTreeSet::from_iter([
3106 NetHsmUserMapping::Signing {
3107 backend_user: "signing1".parse()?,
3108 signing_key_id: "signing1".parse()?,
3109 key_setup: SigningKeySetup::new(
3110 KeyType::Curve25519,
3111 vec![KeyMechanism::EdDsaSignature],
3112 None,
3113 SignatureType::EdDsa,
3114 CryptographicKeyContext::OpenPgp {
3115 user_ids: OpenPgpUserIdList::new(vec![
3116 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3117 ])?,
3118 version: "v4".parse()?,
3119 },
3120 )?,
3121 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAN54Gd1jMz+yNDjBRwX1SnOtWuUsVF64RJIeYJ8DI7b user@host".parse()?,
3122 system_user: "ns1-signing-user1".parse()?,
3123 tag: "signing1".to_string(),
3124 },
3125 NetHsmUserMapping::Signing {
3126 backend_user: "signing1".parse()?,
3127 signing_key_id: "signing1".parse()?,
3128 key_setup: SigningKeySetup::new(
3129 KeyType::Curve25519,
3130 vec![KeyMechanism::EdDsaSignature],
3131 None,
3132 SignatureType::EdDsa,
3133 CryptographicKeyContext::OpenPgp {
3134 user_ids: OpenPgpUserIdList::new(vec![
3135 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3136 ])?,
3137 version: "v4".parse()?,
3138 },
3139 )?,
3140 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3141 system_user: "ns1-signing-user1".parse()?,
3142 tag: "signing1".to_string(),
3143 },
3144 NetHsmUserMapping::Signing {
3145 backend_user: "ns1~signing1".parse()?,
3146 signing_key_id: "signing1".parse()?,
3147 key_setup: SigningKeySetup::new(
3148 KeyType::Curve25519,
3149 vec![KeyMechanism::EdDsaSignature],
3150 None,
3151 SignatureType::EdDsa,
3152 CryptographicKeyContext::OpenPgp {
3153 user_ids: OpenPgpUserIdList::new(vec![
3154 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3155 ])?,
3156 version: "v4".parse()?,
3157 },
3158 )?,
3159 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOh96uFTnvX6P1ebbLxXFvy6sK7qFqlMHDOuJ0TmuXQQ user@host".parse()?,
3160 system_user: "ns1-signing-user1".parse()?,
3161 tag: "signing1".to_string(),
3162 },
3163 NetHsmUserMapping::Signing {
3164 backend_user: "ns1~signing1".parse()?,
3165 signing_key_id: "signing2".parse()?,
3166 key_setup: SigningKeySetup::new(
3167 KeyType::Curve25519,
3168 vec![KeyMechanism::EdDsaSignature],
3169 None,
3170 SignatureType::EdDsa,
3171 CryptographicKeyContext::OpenPgp {
3172 user_ids: OpenPgpUserIdList::new(vec![
3173 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3174 ])?,
3175 version: "v4".parse()?,
3176 },
3177 )?,
3178 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3179 system_user: "ns1-signing-user2".parse()?,
3180 tag: "signing1".to_string(),
3181 },
3182 NetHsmUserMapping::Signing {
3183 backend_user: "ns1~signing2".parse()?,
3184 signing_key_id: "signing2".parse()?,
3185 key_setup: SigningKeySetup::new(
3186 KeyType::Curve25519,
3187 vec![KeyMechanism::EdDsaSignature],
3188 None,
3189 SignatureType::EdDsa,
3190 CryptographicKeyContext::OpenPgp {
3191 user_ids: OpenPgpUserIdList::new(vec![
3192 "Foobar McFooface <foobar@mcfooface.org>".parse()?,
3193 ])?,
3194 version: "v4".parse()?,
3195 },
3196 )?,
3197 ssh_authorized_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDgwGfIRBAsOUuDEZw/uJQZSwOYr4sg2DAZpcc7MfOj user@host".parse()?,
3198 system_user: "ns1-signing-user1".parse()?,
3199 tag: "signing1".to_string(),
3200 },
3201 ]),
3202 )]
3203 fn nethsm_config_new_fails_validation(
3204 #[case] description: &str,
3205 #[case] connections: BTreeSet<Connection>,
3206 #[case] mappings: BTreeSet<NetHsmUserMapping>,
3207 ) -> TestResult {
3208 let error_msg = match NetHsmConfig::new(connections, mappings) {
3209 Err(crate::Error::Validation { source, .. }) => source.to_string(),
3210 Ok(config) => {
3211 panic!("Expected to fail with Error::Validation, but succeeded instead: {config:?}")
3212 }
3213 Err(error) => panic!(
3214 "Expected to fail with Error::Validation, but failed with a different error instead: {error}"
3215 ),
3216 };
3217
3218 with_settings!({
3219 description => description,
3220 snapshot_path => SNAPSHOT_PATH,
3221 prepend_module_to_snapshot => false,
3222 }, {
3223 assert_snapshot!(current().name().expect("current thread should have a name").to_string().replace("::", "__"), error_msg);
3224 });
3225 Ok(())
3226 }
3227
3228 #[rstest]
3230 fn nethsm_config_state_from_config(
3231 nethsm_config: TestResult<NetHsmConfig>,
3232 nethsm_config_mappings: TestResult<BTreeSet<NetHsmUserMapping>>,
3233 ) -> TestResult {
3234 let nethsm_config = nethsm_config?;
3235 let nethsm_config_mappings = nethsm_config_mappings?;
3236 let state = NetHsmConfigState::from(&nethsm_config);
3237
3238 for user_id in nethsm_config_mappings
3239 .iter()
3240 .flat_map(|mapping| mapping.nethsm_user_ids())
3241 {
3242 debug!(
3243 "Ensuring that the NetHSM user ID {user_id} can be found in the NetHSM user state."
3244 );
3245 assert!(
3246 state
3247 .user_data
3248 .iter()
3249 .any(|user_data| user_data.user == &user_id)
3250 );
3251 }
3252
3253 for user_id in nethsm_config_mappings.iter().filter_map(|mapping| {
3254 if let NetHsmUserMapping::Signing { backend_user, .. } = mapping {
3255 Some(backend_user)
3256 } else {
3257 None
3258 }
3259 }) {
3260 debug!(
3261 "Ensuring that the NetHSM user ID {user_id} can be found in the NetHSM key state."
3262 );
3263 assert!(
3264 state
3265 .key_data
3266 .iter()
3267 .any(|user_data| user_data.user == user_id)
3268 );
3269 }
3270
3271 Ok(())
3272 }
3273}