1use std::fmt::Display;
10
11use log::debug;
12#[cfg(doc)]
13use nethsm::NetHsm;
14use nethsm::{
15 CryptographicKeyContext,
16 KeyId,
17 KeyMechanism,
18 KeyType,
19 NamespaceId,
20 UserId,
21 UserRole,
22};
23use nethsm_config::{FilterUserKeys, HermeticParallelConfig};
24#[cfg(doc)]
25use pgp::composed::SignedPublicKey;
26use strum::IntoStaticStr;
27
28use crate::NetHsmBackendError;
29
30#[derive(Debug, thiserror::Error)]
32pub enum StateComparisonError {
33 #[error(
35 "Key mismatch:\n{} (A) => {}\n{} (B) => {}",
36 self_key.state_type, self_key.key_state, other_key.state_type, other_key.key_state
37 )]
38 KeyStateMismatch {
39 self_key: KeyStateType,
41
42 other_key: KeyStateType,
44 },
45
46 #[error(
48 "Keys missing in {other_state_type} (B), but present in {state_type} (A):\n{}",
49 key_states.iter().map(|state| state.to_string()).collect::<Vec<String>>().join("\n"))]
50 UnmatchedKeyStates {
51 state_type: StateType,
53
54 other_state_type: StateType,
56
57 key_states: Vec<KeyState>,
59 },
60
61 #[error(
63 "Users missing in {other_state_type} (B), but present in {state_type} (A):\n{}",
64 user_states.iter().map(|state| state.to_string()).collect::<Vec<String>>().join("\n"))]
65 UnmatchedUserStates {
66 state_type: StateType,
68
69 other_state_type: StateType,
71
72 user_states: Vec<UserState>,
74 },
75
76 #[error(
78 "User mismatch:\n{} (A) => {}\n{} (B) => {}",
79 self_user.state_type, self_user.user_state, other_user.state_type, other_user.user_state
80 )]
81 UserStateMismatch {
82 self_user: UserStateType,
84
85 other_user: UserStateType,
87 },
88}
89
90#[derive(Debug, Default)]
92pub struct StateComparisonErrors {
93 errors: Vec<StateComparisonError>,
94}
95
96impl StateComparisonErrors {
97 pub fn new() -> Self {
99 Default::default()
100 }
101
102 pub fn add(&mut self, elem: StateComparisonError) {
104 self.errors.push(elem)
105 }
106
107 pub fn check(self) -> Result<(), crate::Error> {
113 if !self.errors.is_empty() {
114 return Err(NetHsmBackendError::CompareStates(self).into());
115 }
116
117 Ok(())
118 }
119}
120
121impl Display for StateComparisonErrors {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 for error in self.errors.iter() {
124 writeln!(f, "{error}")?;
125 }
126 Ok(())
127 }
128}
129
130#[derive(Clone, Debug, Eq, PartialEq)]
134pub struct UserState {
135 pub name: UserId,
137 pub role: UserRole,
139 pub tags: Vec<String>,
141}
142
143impl Display for UserState {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 write!(f, "{} (role: {}", self.name, self.role)?;
146 if !self.tags.is_empty() {
147 write!(f, "; tags: {}", self.tags.join(", "))?;
148 }
149 write!(f, ")")?;
150
151 Ok(())
152 }
153}
154
155#[derive(Clone, Debug, Eq, PartialEq)]
160pub enum KeyCertificateState {
161 KeyContext(CryptographicKeyContext),
163
164 Empty,
166
167 Error {
169 message: String,
171 },
172
173 NotACryptographicKeyContext {
175 message: String,
177 },
178
179 NotAnOpenPgpCertificate {
181 message: String,
184 },
185}
186
187impl Display for KeyCertificateState {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 match self {
190 Self::KeyContext(context) => write!(f, "{context}"),
191 Self::Empty => write!(f, "Empty"),
192 Self::Error { message } => {
193 write!(f, "Error retrieving key certificate - {message}")
194 }
195 Self::NotACryptographicKeyContext { message } => {
196 write!(f, "Not a cryptographic key context - \"{message}\"")
197 }
198 Self::NotAnOpenPgpCertificate { message } => {
199 write!(f, "Not an OpenPGP certificate - \"{message}\"")
200 }
201 }
202 }
203}
204
205#[derive(Clone, Debug, Eq, PartialEq)]
209pub struct KeyState {
210 pub name: KeyId,
212 pub namespace: Option<NamespaceId>,
214 pub tags: Vec<String>,
216 pub key_type: KeyType,
218 pub mechanisms: Vec<KeyMechanism>,
220 pub key_cert_state: KeyCertificateState,
222}
223
224impl Display for KeyState {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 write!(f, "{} (", self.name)?;
227 if let Some(namespace) = self.namespace.as_ref() {
228 write!(f, "namespace: {namespace}; ")?;
229 }
230 if !self.tags.is_empty() {
231 write!(f, "tags: {}; ", self.tags.join(", "))?;
232 }
233 write!(f, "type: {}; ", self.key_type)?;
234 write!(
235 f,
236 "mechanisms: {}; ",
237 self.mechanisms
238 .iter()
239 .map(|mechanism| mechanism.to_string())
240 .collect::<Vec<String>>()
241 .join(", ")
242 )?;
243 write!(f, "context: {}", self.key_cert_state)?;
244 write!(f, ")")?;
245
246 Ok(())
247 }
248}
249
250#[derive(Clone, Copy, Debug, strum::Display, Eq, IntoStaticStr, PartialEq)]
252pub enum StateType {
253 #[strum(to_string = "NetHSM")]
255 NetHsm,
256
257 #[strum(to_string = "Signstar configuration")]
259 SignstarConfig,
260}
261
262#[derive(Clone, Debug)]
267pub struct UserStateType {
268 pub state_type: StateType,
270 pub user_state: UserState,
272}
273
274impl Display for UserStateType {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 write!(f, "{} (user): {}", self.state_type, self.user_state)
277 }
278}
279
280#[derive(Clone, Debug)]
285pub struct KeyStateType {
286 pub state_type: StateType,
288 pub key_state: KeyState,
290}
291
292impl Display for KeyStateType {
293 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294 write!(f, "{} (key): {}", self.state_type, self.key_state)
295 }
296}
297
298#[derive(Clone, Debug)]
302pub struct State {
303 pub state_type: StateType,
305 pub users: Vec<UserState>,
307 pub keys: Vec<KeyState>,
309}
310
311impl State {
312 pub fn compare(&self, other: &State) -> Result<(), crate::Error> {
321 debug!(
322 "Create state diff of {} ({} users, {} keys) and {} ({} users, {} keys)",
323 self.state_type,
324 self.users.len(),
325 self.keys.len(),
326 other.state_type,
327 other.users.len(),
328 other.keys.len()
329 );
330 let mut errors = StateComparisonErrors::new();
331
332 let (unmatched_self_users, matched_other_users) = {
335 let mut unmatched_self_users = Vec::new();
336 let mut matched_other_users = Vec::new();
337
338 for self_user in self.users.iter() {
341 let Some(other_user) = other.users.iter().find(|user| user.name == self_user.name)
342 else {
343 unmatched_self_users.push(self_user);
344 continue;
345 };
346
347 matched_other_users.push(other_user);
348 if self_user != other_user {
349 errors.add(StateComparisonError::UserStateMismatch {
350 self_user: UserStateType {
351 state_type: self.state_type,
352 user_state: self_user.clone(),
353 },
354 other_user: UserStateType {
355 state_type: other.state_type,
356 user_state: other_user.clone(),
357 },
358 });
359 continue;
360 }
361 }
362
363 (unmatched_self_users, matched_other_users)
364 };
365
366 if !unmatched_self_users.is_empty() {
368 errors.add(StateComparisonError::UnmatchedUserStates {
369 state_type: self.state_type,
370 other_state_type: other.state_type,
371 user_states: unmatched_self_users.into_iter().cloned().collect(),
372 });
373 }
374
375 {
376 let mut unmatched_other_users = Vec::new();
378 if matched_other_users.len() != other.users.len() {
379 for other_user in other.users.iter() {
380 if !matched_other_users.contains(&other_user) {
381 unmatched_other_users.push(other_user);
382 };
384 }
385 }
386 if !unmatched_other_users.is_empty() {
387 errors.add(StateComparisonError::UnmatchedUserStates {
388 state_type: other.state_type,
389 other_state_type: self.state_type,
390 user_states: unmatched_other_users.into_iter().cloned().collect(),
391 });
392 }
393 }
394
395 let (unmatched_self_keys, matched_other_keys) =
398 {
399 let mut unmatched_self_keys = Vec::new();
400 let mut matched_other_keys = Vec::new();
401
402 for self_key in self.keys.iter() {
405 let Some(other_key) = other.keys.iter().find(|key| {
406 key.name == self_key.name && key.namespace == self_key.namespace
407 }) else {
408 unmatched_self_keys.push(self_key);
409 continue;
410 };
411
412 matched_other_keys.push(other_key);
413
414 if (matches!(self_key.key_cert_state, KeyCertificateState::Empty)
418 && matches!(
419 other_key.key_cert_state,
420 KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
421 ))
422 || (matches!(
423 self_key.key_cert_state,
424 KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
425 ) && matches!(other_key.key_cert_state, KeyCertificateState::Empty))
426 {
427 continue;
428 }
429
430 if self_key != other_key {
431 errors.add(StateComparisonError::KeyStateMismatch {
432 self_key: KeyStateType {
433 state_type: self.state_type,
434 key_state: self_key.clone(),
435 },
436 other_key: KeyStateType {
437 state_type: other.state_type,
438 key_state: other_key.clone(),
439 },
440 })
441 }
442 }
443
444 (unmatched_self_keys, matched_other_keys)
445 };
446
447 if !unmatched_self_keys.is_empty() {
449 errors.add(StateComparisonError::UnmatchedKeyStates {
450 state_type: self.state_type,
451 other_state_type: other.state_type,
452 key_states: unmatched_self_keys.into_iter().cloned().collect(),
453 });
454 }
455
456 {
457 let mut unmatched_other_keys = Vec::new();
459 if matched_other_keys.len() != other.keys.len() {
460 for other_key in other.keys.iter() {
461 if !matched_other_keys.contains(&other_key) {
462 unmatched_other_keys.push(other_key);
463 continue;
464 };
465 }
466 }
467 if !unmatched_other_keys.is_empty() {
468 errors.add(StateComparisonError::UnmatchedKeyStates {
469 state_type: other.state_type,
470 other_state_type: self.state_type,
471 key_states: unmatched_other_keys.into_iter().cloned().collect(),
472 });
473 }
474 }
475
476 errors.check()?;
477
478 Ok(())
479 }
480}
481
482impl From<&HermeticParallelConfig> for State {
483 fn from(value: &HermeticParallelConfig) -> Self {
485 let users: Vec<UserState> = value
486 .iter_user_mappings()
487 .flat_map(|mapping| {
488 mapping
489 .get_nethsm_user_role_and_tags()
490 .iter()
491 .map(|(name, role, tags)| UserState {
492 name: name.clone(),
493 role: *role,
494 tags: tags.clone(),
495 })
496 .collect::<Vec<UserState>>()
497 })
498 .collect();
499 let keys: Vec<KeyState> = value
500 .iter_user_mappings()
501 .flat_map(|mapping| {
502 mapping
503 .get_nethsm_user_key_and_tag(FilterUserKeys::All)
504 .iter()
505 .map(|(user_id, key_setup, tag)| KeyState {
506 name: key_setup.get_key_id(),
507 namespace: user_id.namespace().cloned(),
508 tags: vec![tag.to_string()],
509 key_type: key_setup.get_key_type(),
510 mechanisms: key_setup.get_key_mechanisms(),
511 key_cert_state: KeyCertificateState::KeyContext(
512 key_setup.get_key_context(),
513 ),
514 })
515 .collect::<Vec<KeyState>>()
516 })
517 .collect();
518
519 Self {
520 state_type: StateType::SignstarConfig,
521 users,
522 keys,
523 }
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use log::{LevelFilter, debug};
530 use nethsm::OpenPgpUserIdList;
531 use rstest::rstest;
532 use simplelog::{ColorChoice, Config, TermLogger, TerminalMode};
533 use testresult::TestResult;
534
535 use super::*;
536
537 fn init_logger() {
539 if TermLogger::init(
540 LevelFilter::Trace,
541 Config::default(),
542 TerminalMode::Stderr,
543 ColorChoice::Auto,
544 )
545 .is_err()
546 {
547 debug!("Not initializing another logger, as one is initialized already.");
548 }
549 }
550
551 #[rstest]
553 #[case(
554 UserState{
555 name: "testuser".parse()?,
556 role: UserRole::Operator,
557 tags: vec!["tag1".to_string(), "tag2".to_string()]
558 },
559 "testuser (role: Operator; tags: tag1, tag2)",
560 )]
561 #[case(
562 UserState{
563 name: "testuser".parse()?,
564 role: UserRole::Operator,
565 tags: Vec::new(),
566 },
567 "testuser (role: Operator)",
568 )]
569 #[case(
570 UserState{
571 name: "testuser".parse()?,
572 role: UserRole::Metrics,
573 tags: Vec::new(),
574 },
575 "testuser (role: Metrics)",
576 )]
577 #[case(
578 UserState{
579 name: "testuser".parse()?,
580 role: UserRole::Backup,
581 tags: Vec::new(),
582 },
583 "testuser (role: Backup)",
584 )]
585 #[case(
586 UserState{name:
587 "testuser".parse()?,
588 role: UserRole::Administrator,
589 tags: Vec::new(),
590 },
591 "testuser (role: Administrator)",
592 )]
593 fn user_state_to_string(#[case] user_state: UserState, #[case] expected: &str) -> TestResult {
594 init_logger();
595
596 assert_eq!(user_state.to_string(), expected);
597 Ok(())
598 }
599
600 #[rstest]
602 #[case::namespaced_key_with_openpgp_v4_cert(
603 KeyState{
604 name: "key1".parse()?,
605 namespace: Some("ns1".parse()?),
606 tags: vec!["tag1".to_string(), "tag2".to_string()],
607 key_type: KeyType::Curve25519,
608 mechanisms: vec![KeyMechanism::EdDsaSignature],
609 key_cert_state: KeyCertificateState::KeyContext(
610 CryptographicKeyContext::OpenPgp {
611 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
612 version: nethsm::OpenPgpVersion::V4,
613 })
614 },
615 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: \"Foobar McFooface <foobar@mcfooface.org>\"))",
616 )]
617 #[case::namespaced_key_with_raw_cert(
618 KeyState{
619 name: "key1".parse()?,
620 namespace: Some("ns1".parse()?),
621 tags: vec!["tag1".to_string(), "tag2".to_string()],
622 key_type: KeyType::Curve25519,
623 mechanisms: vec![KeyMechanism::EdDsaSignature],
624 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
625 },
626 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
627 )]
628 #[case::namespaced_key_with_no_cert(
629 KeyState{
630 name: "key1".parse()?,
631 namespace: Some("ns1".parse()?),
632 tags: vec!["tag1".to_string(), "tag2".to_string()],
633 key_type: KeyType::Curve25519,
634 mechanisms: vec![KeyMechanism::EdDsaSignature],
635 key_cert_state: KeyCertificateState::Empty
636 },
637 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Empty)",
638 )]
639 #[case::namespaced_key_with_cert_error(
640 KeyState{
641 name: "key1".parse()?,
642 namespace: Some("ns1".parse()?),
643 tags: vec!["tag1".to_string(), "tag2".to_string()],
644 key_type: KeyType::Curve25519,
645 mechanisms: vec![KeyMechanism::EdDsaSignature],
646 key_cert_state: KeyCertificateState::Error { message: "the dog ate it".to_string() }
647 },
648 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Error retrieving key certificate - the dog ate it)",
649 )]
650 #[case::namespaced_key_with_not_a_cert_context(
651 KeyState{
652 name: "key1".parse()?,
653 namespace: Some("ns1".parse()?),
654 tags: vec!["tag1".to_string(), "tag2".to_string()],
655 key_type: KeyType::Curve25519,
656 mechanisms: vec![KeyMechanism::EdDsaSignature],
657 key_cert_state: KeyCertificateState::NotACryptographicKeyContext { message: "failed to convert".to_string() }
658 },
659 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Not a cryptographic key context - \"failed to convert\")",
660 )]
661 #[case::namespaced_key_with_not_an_openpgp_cert(
662 KeyState{
663 name: "key1".parse()?,
664 namespace: Some("ns1".parse()?),
665 tags: vec!["tag1".to_string(), "tag2".to_string()],
666 key_type: KeyType::Curve25519,
667 mechanisms: vec![KeyMechanism::EdDsaSignature],
668 key_cert_state: KeyCertificateState::NotAnOpenPgpCertificate { message: "it's a blob".to_string() }
669 },
670 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Not an OpenPGP certificate - \"it's a blob\")",
671 )]
672 #[case::system_wide_key_with_no_cert_and_no_tags_and_raw_cert(
673 KeyState{
674 name: "key1".parse()?,
675 namespace: None,
676 tags: Vec::new(),
677 key_type: KeyType::Curve25519,
678 mechanisms: vec![KeyMechanism::EdDsaSignature],
679 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
680 },
681 "key1 (type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
682 )]
683 fn key_state_to_string(#[case] key_state: KeyState, #[case] expected: &str) -> TestResult {
684 init_logger();
685
686 assert_eq!(key_state.to_string(), expected);
687 Ok(())
688 }
689
690 #[rstest]
692 #[case(StateType::NetHsm, "NetHSM")]
693 #[case(StateType::SignstarConfig, "Signstar configuration")]
694 fn state_type_display(#[case] state_type: StateType, #[case] expected: &str) -> TestResult {
695 init_logger();
696
697 assert_eq!(state_type.to_string(), expected);
698 Ok(())
699 }
700
701 #[rstest]
703 #[case(
704 KeyStateType{
705 state_type: StateType::NetHsm,
706 key_state: KeyState{
707 name: "key1".parse()?,
708 namespace: None,
709 tags: Vec::new(),
710 key_type: KeyType::Curve25519,
711 mechanisms: vec![KeyMechanism::EdDsaSignature],
712 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
713 },
714 },
715 "NetHSM (key): key1 (type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
716 )]
717 #[case(
718 KeyStateType{
719 state_type: StateType::SignstarConfig,
720 key_state: KeyState{
721 name: "key1".parse()?,
722 namespace: None,
723 tags: Vec::new(),
724 key_type: KeyType::Curve25519,
725 mechanisms: vec![KeyMechanism::EdDsaSignature],
726 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
727 },
728 },
729 "Signstar configuration (key): key1 (type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
730 )]
731 fn key_state_type_display(
732 #[case] state_type: KeyStateType,
733 #[case] expected: &str,
734 ) -> TestResult {
735 init_logger();
736
737 assert_eq!(state_type.to_string(), expected);
738 Ok(())
739 }
740
741 #[rstest]
743 #[case(
744 UserStateType{
745 state_type: StateType::NetHsm,
746 user_state: UserState{
747 name: "testuser".parse()?,
748 role: UserRole::Administrator,
749 tags: Vec::new(),
750 }
751 },
752 "NetHSM (user): testuser (role: Administrator)",
753 )]
754 #[case(
755 UserStateType{
756 state_type: StateType::SignstarConfig,
757 user_state: UserState{
758 name: "testuser".parse()?,
759 role: UserRole::Administrator,
760 tags: Vec::new(),
761 },
762 },
763 "Signstar configuration (user): testuser (role: Administrator)",
764 )]
765 fn user_state_type_display(
766 #[case] state_type: UserStateType,
767 #[case] expected: &str,
768 ) -> TestResult {
769 init_logger();
770
771 assert_eq!(state_type.to_string(), expected);
772 Ok(())
773 }
774
775 #[rstest]
777 #[case::empty(
778 State {
779 state_type: StateType::SignstarConfig,
780 users: Vec::new(),
781 keys: Vec::new(),
782 },
783 State {
784 state_type: StateType::NetHsm,
785 users: Vec::new(),
786 keys: Vec::new(),
787 },
788 )]
789 #[case::with_users_and_keys(
790 State {
791 state_type: StateType::SignstarConfig,
792 users: vec![
793 UserState{
794 name: "operator1".parse()?,
795 role: UserRole::Operator,
796 tags: vec!["tag1".to_string()]
797 },
798 UserState{
799 name: "admin".parse()?,
800 role: UserRole::Administrator,
801 tags: Vec::new(),
802 },
803 ],
804 keys: vec![
805 KeyState{
806 name: "key1".parse()?,
807 namespace: None,
808 tags: vec!["tag1".to_string()],
809 key_type: KeyType::Curve25519,
810 mechanisms: vec![KeyMechanism::EdDsaSignature],
811 key_cert_state: KeyCertificateState::KeyContext(
812 CryptographicKeyContext::OpenPgp {
813 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
814 version: nethsm::OpenPgpVersion::V4,
815 }
816 )
817 },
818 ],
819 },
820 State {
821 state_type: StateType::NetHsm,
822 users: vec![
823 UserState{
824 name: "operator1".parse()?,
825 role: UserRole::Operator,
826 tags: vec!["tag1".to_string()]
827 },
828 UserState{
829 name: "admin".parse()?,
830 role: UserRole::Administrator,
831 tags: Vec::new(),
832 },
833 ],
834 keys: vec![
835 KeyState{
836 name: "key1".parse()?,
837 namespace: None,
838 tags: vec!["tag1".to_string()],
839 key_type: KeyType::Curve25519,
840 mechanisms: vec![KeyMechanism::EdDsaSignature],
841 key_cert_state: KeyCertificateState::KeyContext(
842 CryptographicKeyContext::OpenPgp {
843 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
844 version: nethsm::OpenPgpVersion::V4,
845 }
846 )
847 },
848 ],
849 },
850 )]
851 fn state_compare_succeeds(#[case] state_a: State, #[case] state_b: State) -> TestResult {
852 init_logger();
853
854 let compare_result = state_a.compare(&state_b);
855
856 if let Err(error) = compare_result {
857 panic!("Comparison should have succeeded but failed:\n{error}")
858 }
859
860 Ok(())
861 }
862
863 #[rstest]
865 #[case::one_empty(
866 State {
867 state_type: StateType::SignstarConfig,
868 users: vec![
869 UserState{
870 name: "operator1".parse()?,
871 role: UserRole::Operator,
872 tags: vec!["tag1".to_string()]
873 },
874 UserState{
875 name: "admin".parse()?,
876 role: UserRole::Administrator,
877 tags: Vec::new(),
878 },
879 ],
880 keys: vec![
881 KeyState{
882 name: "key1".parse()?,
883 namespace: None,
884 tags: vec!["tag1".to_string()],
885 key_type: KeyType::Curve25519,
886 mechanisms: vec![KeyMechanism::EdDsaSignature],
887 key_cert_state: KeyCertificateState::KeyContext(
888 CryptographicKeyContext::OpenPgp {
889 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
890 version: nethsm::OpenPgpVersion::V4,
891 }
892 )
893 },
894 ],
895 },
896 State {
897 state_type: StateType::NetHsm,
898 users: Vec::new(),
899 keys: Vec::new(),
900 },
901 r#"NetHSM backend error:
902Errors occurred when comparing states:
903Users missing in NetHSM (B), but present in Signstar configuration (A):
904operator1 (role: Operator; tags: tag1)
905admin (role: Administrator)
906Keys missing in NetHSM (B), but present in Signstar configuration (A):
907key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
908"#,
909 )]
910 #[case::differing_users_and_keys(
911 State {
912 state_type: StateType::SignstarConfig,
913 users: vec![
914 UserState{
915 name: "operator1".parse()?,
916 role: UserRole::Operator,
917 tags: vec!["tag1".to_string()]
918 },
919 UserState{
920 name: "admin".parse()?,
921 role: UserRole::Administrator,
922 tags: Vec::new(),
923 },
924 ],
925 keys: vec![
926 KeyState{
927 name: "key1".parse()?,
928 namespace: None,
929 tags: vec!["tag1".to_string()],
930 key_type: KeyType::Curve25519,
931 mechanisms: vec![KeyMechanism::EdDsaSignature],
932 key_cert_state: KeyCertificateState::KeyContext(
933 CryptographicKeyContext::OpenPgp {
934 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
935 version: nethsm::OpenPgpVersion::V4,
936 }
937 )
938 },
939 ],
940 },
941 State {
942 state_type: StateType::NetHsm,
943 users: vec![
944 UserState{
945 name: "operator2".parse()?,
946 role: UserRole::Operator,
947 tags: vec!["tag2".to_string(), "tag3".to_string()]
948 },
949 UserState{
950 name: "admin".parse()?,
951 role: UserRole::Administrator,
952 tags: Vec::new(),
953 },
954 ],
955 keys: vec![
956 KeyState{
957 name: "key2".parse()?,
958 namespace: None,
959 tags: vec!["tag2".to_string()],
960 key_type: KeyType::Curve25519,
961 mechanisms: vec![KeyMechanism::EdDsaSignature],
962 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
963 },
964 KeyState{
965 name: "key3".parse()?,
966 namespace: None,
967 tags: vec!["tag3".to_string()],
968 key_type: KeyType::Curve25519,
969 mechanisms: vec![KeyMechanism::EdDsaSignature],
970 key_cert_state: KeyCertificateState::KeyContext(
971 CryptographicKeyContext::OpenPgp {
972 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
973 version: nethsm::OpenPgpVersion::V4,
974 }
975 )
976 },
977 ],
978 },
979 r#"NetHSM backend error:
980Errors occurred when comparing states:
981Users missing in NetHSM (B), but present in Signstar configuration (A):
982operator1 (role: Operator; tags: tag1)
983Users missing in Signstar configuration (B), but present in NetHSM (A):
984operator2 (role: Operator; tags: tag2, tag3)
985Keys missing in NetHSM (B), but present in Signstar configuration (A):
986key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
987Keys missing in Signstar configuration (B), but present in NetHSM (A):
988key2 (tags: tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Raw)
989key3 (tags: tag3; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
990"#,
991 )]
992 #[case::user_and_key_mismatch(
993 State {
994 state_type: StateType::SignstarConfig,
995 users: vec![
996 UserState{
997 name: "operator1".parse()?,
998 role: UserRole::Operator,
999 tags: vec!["tag1".to_string()]
1000 },
1001 UserState{
1002 name: "admin".parse()?,
1003 role: UserRole::Administrator,
1004 tags: Vec::new(),
1005 },
1006 ],
1007 keys: vec![
1008 KeyState{
1009 name: "key1".parse()?,
1010 namespace: None,
1011 tags: vec!["tag1".to_string()],
1012 key_type: KeyType::Curve25519,
1013 mechanisms: vec![KeyMechanism::EdDsaSignature],
1014 key_cert_state: KeyCertificateState::KeyContext(
1015 CryptographicKeyContext::OpenPgp {
1016 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
1017 version: nethsm::OpenPgpVersion::V4,
1018 }
1019 )
1020 },
1021 ],
1022 },
1023 State {
1024 state_type: StateType::NetHsm,
1025 users: vec![
1026 UserState{
1027 name: "operator1".parse()?,
1028 role: UserRole::Metrics,
1029 tags: Vec::new(),
1030 },
1031 UserState{
1032 name: "admin".parse()?,
1033 role: UserRole::Administrator,
1034 tags: Vec::new(),
1035 },
1036 ],
1037 keys: vec![
1038 KeyState{
1039 name: "key1".parse()?,
1040 namespace: None,
1041 tags: vec!["tag1".to_string()],
1042 key_type: KeyType::Curve25519,
1043 mechanisms: vec![KeyMechanism::EdDsaSignature],
1044 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
1045 },
1046 ],
1047 },
1048 r#"NetHSM backend error:
1049Errors occurred when comparing states:
1050User mismatch:
1051Signstar configuration (A) => operator1 (role: Operator; tags: tag1)
1052NetHSM (B) => operator1 (role: Metrics)
1053Key mismatch:
1054Signstar configuration (A) => key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
1055NetHSM (B) => key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: Raw)
1056"#,
1057 )]
1058 fn state_compare_fails(
1059 #[case] state_a: State,
1060 #[case] state_b: State,
1061 #[case] expected: &str,
1062 ) -> TestResult {
1063 init_logger();
1064
1065 let compare_result = state_a.compare(&state_b);
1066
1067 match compare_result {
1068 Ok(_) => panic!("Comparison should have failed but succeeded"),
1069 Err(error) => assert_eq!(error.to_string(), expected),
1070 }
1071
1072 Ok(())
1073 }
1074}