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