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