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};
23#[cfg(doc)]
24use pgp::composed::SignedPublicKey;
25use strum::IntoStaticStr;
26
27use crate::NetHsmBackendError;
28use crate::{FilterUserKeys, SignstarConfig};
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<&SignstarConfig> for State {
483 fn from(value: &SignstarConfig) -> 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;
530 use nethsm::OpenPgpUserIdList;
531 use rstest::rstest;
532 use signstar_common::logging::setup_logging;
533 use testresult::TestResult;
534
535 use super::*;
536
537 #[rstest]
539 #[case(
540 UserState{
541 name: "testuser".parse()?,
542 role: UserRole::Operator,
543 tags: vec!["tag1".to_string(), "tag2".to_string()]
544 },
545 "testuser (role: Operator; tags: tag1, tag2)",
546 )]
547 #[case(
548 UserState{
549 name: "testuser".parse()?,
550 role: UserRole::Operator,
551 tags: Vec::new(),
552 },
553 "testuser (role: Operator)",
554 )]
555 #[case(
556 UserState{
557 name: "testuser".parse()?,
558 role: UserRole::Metrics,
559 tags: Vec::new(),
560 },
561 "testuser (role: Metrics)",
562 )]
563 #[case(
564 UserState{
565 name: "testuser".parse()?,
566 role: UserRole::Backup,
567 tags: Vec::new(),
568 },
569 "testuser (role: Backup)",
570 )]
571 #[case(
572 UserState{name:
573 "testuser".parse()?,
574 role: UserRole::Administrator,
575 tags: Vec::new(),
576 },
577 "testuser (role: Administrator)",
578 )]
579 fn user_state_to_string(#[case] user_state: UserState, #[case] expected: &str) -> TestResult {
580 setup_logging(LevelFilter::Debug)?;
581
582 assert_eq!(user_state.to_string(), expected);
583 Ok(())
584 }
585
586 #[rstest]
588 #[case::namespaced_key_with_openpgp_v4_cert(
589 KeyState{
590 name: "key1".parse()?,
591 namespace: Some("ns1".parse()?),
592 tags: vec!["tag1".to_string(), "tag2".to_string()],
593 key_type: KeyType::Curve25519,
594 mechanisms: vec![KeyMechanism::EdDsaSignature],
595 key_cert_state: KeyCertificateState::KeyContext(
596 CryptographicKeyContext::OpenPgp {
597 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
598 version: nethsm::OpenPgpVersion::V4,
599 })
600 },
601 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: \"Foobar McFooface <foobar@mcfooface.org>\"))",
602 )]
603 #[case::namespaced_key_with_raw_cert(
604 KeyState{
605 name: "key1".parse()?,
606 namespace: Some("ns1".parse()?),
607 tags: vec!["tag1".to_string(), "tag2".to_string()],
608 key_type: KeyType::Curve25519,
609 mechanisms: vec![KeyMechanism::EdDsaSignature],
610 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
611 },
612 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
613 )]
614 #[case::namespaced_key_with_no_cert(
615 KeyState{
616 name: "key1".parse()?,
617 namespace: Some("ns1".parse()?),
618 tags: vec!["tag1".to_string(), "tag2".to_string()],
619 key_type: KeyType::Curve25519,
620 mechanisms: vec![KeyMechanism::EdDsaSignature],
621 key_cert_state: KeyCertificateState::Empty
622 },
623 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Empty)",
624 )]
625 #[case::namespaced_key_with_cert_error(
626 KeyState{
627 name: "key1".parse()?,
628 namespace: Some("ns1".parse()?),
629 tags: vec!["tag1".to_string(), "tag2".to_string()],
630 key_type: KeyType::Curve25519,
631 mechanisms: vec![KeyMechanism::EdDsaSignature],
632 key_cert_state: KeyCertificateState::Error { message: "the dog ate it".to_string() }
633 },
634 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Error retrieving key certificate - the dog ate it)",
635 )]
636 #[case::namespaced_key_with_not_a_cert_context(
637 KeyState{
638 name: "key1".parse()?,
639 namespace: Some("ns1".parse()?),
640 tags: vec!["tag1".to_string(), "tag2".to_string()],
641 key_type: KeyType::Curve25519,
642 mechanisms: vec![KeyMechanism::EdDsaSignature],
643 key_cert_state: KeyCertificateState::NotACryptographicKeyContext { message: "failed to convert".to_string() }
644 },
645 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Not a cryptographic key context - \"failed to convert\")",
646 )]
647 #[case::namespaced_key_with_not_an_openpgp_cert(
648 KeyState{
649 name: "key1".parse()?,
650 namespace: Some("ns1".parse()?),
651 tags: vec!["tag1".to_string(), "tag2".to_string()],
652 key_type: KeyType::Curve25519,
653 mechanisms: vec![KeyMechanism::EdDsaSignature],
654 key_cert_state: KeyCertificateState::NotAnOpenPgpCertificate { message: "it's a blob".to_string() }
655 },
656 "key1 (namespace: ns1; tags: tag1, tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Not an OpenPGP certificate - \"it's a blob\")",
657 )]
658 #[case::system_wide_key_with_no_cert_and_no_tags_and_raw_cert(
659 KeyState{
660 name: "key1".parse()?,
661 namespace: None,
662 tags: Vec::new(),
663 key_type: KeyType::Curve25519,
664 mechanisms: vec![KeyMechanism::EdDsaSignature],
665 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
666 },
667 "key1 (type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
668 )]
669 fn key_state_to_string(#[case] key_state: KeyState, #[case] expected: &str) -> TestResult {
670 setup_logging(LevelFilter::Debug)?;
671
672 assert_eq!(key_state.to_string(), expected);
673 Ok(())
674 }
675
676 #[rstest]
678 #[case(StateType::NetHsm, "NetHSM")]
679 #[case(StateType::SignstarConfig, "Signstar configuration")]
680 fn state_type_display(#[case] state_type: StateType, #[case] expected: &str) -> TestResult {
681 setup_logging(LevelFilter::Debug)?;
682
683 assert_eq!(state_type.to_string(), expected);
684 Ok(())
685 }
686
687 #[rstest]
689 #[case(
690 KeyStateType{
691 state_type: StateType::NetHsm,
692 key_state: KeyState{
693 name: "key1".parse()?,
694 namespace: None,
695 tags: Vec::new(),
696 key_type: KeyType::Curve25519,
697 mechanisms: vec![KeyMechanism::EdDsaSignature],
698 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
699 },
700 },
701 "NetHSM (key): key1 (type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
702 )]
703 #[case(
704 KeyStateType{
705 state_type: StateType::SignstarConfig,
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 "Signstar configuration (key): key1 (type: Curve25519; mechanisms: EdDsaSignature; context: Raw)",
716 )]
717 fn key_state_type_display(
718 #[case] state_type: KeyStateType,
719 #[case] expected: &str,
720 ) -> TestResult {
721 setup_logging(LevelFilter::Debug)?;
722
723 assert_eq!(state_type.to_string(), expected);
724 Ok(())
725 }
726
727 #[rstest]
729 #[case(
730 UserStateType{
731 state_type: StateType::NetHsm,
732 user_state: UserState{
733 name: "testuser".parse()?,
734 role: UserRole::Administrator,
735 tags: Vec::new(),
736 }
737 },
738 "NetHSM (user): testuser (role: Administrator)",
739 )]
740 #[case(
741 UserStateType{
742 state_type: StateType::SignstarConfig,
743 user_state: UserState{
744 name: "testuser".parse()?,
745 role: UserRole::Administrator,
746 tags: Vec::new(),
747 },
748 },
749 "Signstar configuration (user): testuser (role: Administrator)",
750 )]
751 fn user_state_type_display(
752 #[case] state_type: UserStateType,
753 #[case] expected: &str,
754 ) -> TestResult {
755 setup_logging(LevelFilter::Debug)?;
756
757 assert_eq!(state_type.to_string(), expected);
758 Ok(())
759 }
760
761 #[rstest]
763 #[case::empty(
764 State {
765 state_type: StateType::SignstarConfig,
766 users: Vec::new(),
767 keys: Vec::new(),
768 },
769 State {
770 state_type: StateType::NetHsm,
771 users: Vec::new(),
772 keys: Vec::new(),
773 },
774 )]
775 #[case::with_users_and_keys(
776 State {
777 state_type: StateType::SignstarConfig,
778 users: vec![
779 UserState{
780 name: "operator1".parse()?,
781 role: UserRole::Operator,
782 tags: vec!["tag1".to_string()]
783 },
784 UserState{
785 name: "admin".parse()?,
786 role: UserRole::Administrator,
787 tags: Vec::new(),
788 },
789 ],
790 keys: vec![
791 KeyState{
792 name: "key1".parse()?,
793 namespace: None,
794 tags: vec!["tag1".to_string()],
795 key_type: KeyType::Curve25519,
796 mechanisms: vec![KeyMechanism::EdDsaSignature],
797 key_cert_state: KeyCertificateState::KeyContext(
798 CryptographicKeyContext::OpenPgp {
799 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
800 version: nethsm::OpenPgpVersion::V4,
801 }
802 )
803 },
804 ],
805 },
806 State {
807 state_type: StateType::NetHsm,
808 users: vec![
809 UserState{
810 name: "operator1".parse()?,
811 role: UserRole::Operator,
812 tags: vec!["tag1".to_string()]
813 },
814 UserState{
815 name: "admin".parse()?,
816 role: UserRole::Administrator,
817 tags: Vec::new(),
818 },
819 ],
820 keys: vec![
821 KeyState{
822 name: "key1".parse()?,
823 namespace: None,
824 tags: vec!["tag1".to_string()],
825 key_type: KeyType::Curve25519,
826 mechanisms: vec![KeyMechanism::EdDsaSignature],
827 key_cert_state: KeyCertificateState::KeyContext(
828 CryptographicKeyContext::OpenPgp {
829 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
830 version: nethsm::OpenPgpVersion::V4,
831 }
832 )
833 },
834 ],
835 },
836 )]
837 fn state_compare_succeeds(#[case] state_a: State, #[case] state_b: State) -> TestResult {
838 setup_logging(LevelFilter::Debug)?;
839
840 let compare_result = state_a.compare(&state_b);
841
842 if let Err(error) = compare_result {
843 panic!("Comparison should have succeeded but failed:\n{error}")
844 }
845
846 Ok(())
847 }
848
849 #[rstest]
851 #[case::one_empty(
852 State {
853 state_type: StateType::SignstarConfig,
854 users: vec![
855 UserState{
856 name: "operator1".parse()?,
857 role: UserRole::Operator,
858 tags: vec!["tag1".to_string()]
859 },
860 UserState{
861 name: "admin".parse()?,
862 role: UserRole::Administrator,
863 tags: Vec::new(),
864 },
865 ],
866 keys: vec![
867 KeyState{
868 name: "key1".parse()?,
869 namespace: None,
870 tags: vec!["tag1".to_string()],
871 key_type: KeyType::Curve25519,
872 mechanisms: vec![KeyMechanism::EdDsaSignature],
873 key_cert_state: KeyCertificateState::KeyContext(
874 CryptographicKeyContext::OpenPgp {
875 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
876 version: nethsm::OpenPgpVersion::V4,
877 }
878 )
879 },
880 ],
881 },
882 State {
883 state_type: StateType::NetHsm,
884 users: Vec::new(),
885 keys: Vec::new(),
886 },
887 r#"NetHSM backend error:
888Errors occurred when comparing states:
889Users missing in NetHSM (B), but present in Signstar configuration (A):
890operator1 (role: Operator; tags: tag1)
891admin (role: Administrator)
892Keys missing in NetHSM (B), but present in Signstar configuration (A):
893key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
894"#,
895 )]
896 #[case::differing_users_and_keys(
897 State {
898 state_type: StateType::SignstarConfig,
899 users: vec![
900 UserState{
901 name: "operator1".parse()?,
902 role: UserRole::Operator,
903 tags: vec!["tag1".to_string()]
904 },
905 UserState{
906 name: "admin".parse()?,
907 role: UserRole::Administrator,
908 tags: Vec::new(),
909 },
910 ],
911 keys: vec![
912 KeyState{
913 name: "key1".parse()?,
914 namespace: None,
915 tags: vec!["tag1".to_string()],
916 key_type: KeyType::Curve25519,
917 mechanisms: vec![KeyMechanism::EdDsaSignature],
918 key_cert_state: KeyCertificateState::KeyContext(
919 CryptographicKeyContext::OpenPgp {
920 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
921 version: nethsm::OpenPgpVersion::V4,
922 }
923 )
924 },
925 ],
926 },
927 State {
928 state_type: StateType::NetHsm,
929 users: vec![
930 UserState{
931 name: "operator2".parse()?,
932 role: UserRole::Operator,
933 tags: vec!["tag2".to_string(), "tag3".to_string()]
934 },
935 UserState{
936 name: "admin".parse()?,
937 role: UserRole::Administrator,
938 tags: Vec::new(),
939 },
940 ],
941 keys: vec![
942 KeyState{
943 name: "key2".parse()?,
944 namespace: None,
945 tags: vec!["tag2".to_string()],
946 key_type: KeyType::Curve25519,
947 mechanisms: vec![KeyMechanism::EdDsaSignature],
948 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
949 },
950 KeyState{
951 name: "key3".parse()?,
952 namespace: None,
953 tags: vec!["tag3".to_string()],
954 key_type: KeyType::Curve25519,
955 mechanisms: vec![KeyMechanism::EdDsaSignature],
956 key_cert_state: KeyCertificateState::KeyContext(
957 CryptographicKeyContext::OpenPgp {
958 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
959 version: nethsm::OpenPgpVersion::V4,
960 }
961 )
962 },
963 ],
964 },
965 r#"NetHSM backend error:
966Errors occurred when comparing states:
967Users missing in NetHSM (B), but present in Signstar configuration (A):
968operator1 (role: Operator; tags: tag1)
969Users missing in Signstar configuration (B), but present in NetHSM (A):
970operator2 (role: Operator; tags: tag2, tag3)
971Keys missing in NetHSM (B), but present in Signstar configuration (A):
972key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
973Keys missing in Signstar configuration (B), but present in NetHSM (A):
974key2 (tags: tag2; type: Curve25519; mechanisms: EdDsaSignature; context: Raw)
975key3 (tags: tag3; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
976"#,
977 )]
978 #[case::user_and_key_mismatch(
979 State {
980 state_type: StateType::SignstarConfig,
981 users: vec![
982 UserState{
983 name: "operator1".parse()?,
984 role: UserRole::Operator,
985 tags: vec!["tag1".to_string()]
986 },
987 UserState{
988 name: "admin".parse()?,
989 role: UserRole::Administrator,
990 tags: Vec::new(),
991 },
992 ],
993 keys: vec![
994 KeyState{
995 name: "key1".parse()?,
996 namespace: None,
997 tags: vec!["tag1".to_string()],
998 key_type: KeyType::Curve25519,
999 mechanisms: vec![KeyMechanism::EdDsaSignature],
1000 key_cert_state: KeyCertificateState::KeyContext(
1001 CryptographicKeyContext::OpenPgp {
1002 user_ids: OpenPgpUserIdList::new(vec!["Foobar McFooface <foobar@mcfooface.org>".parse()?])?,
1003 version: nethsm::OpenPgpVersion::V4,
1004 }
1005 )
1006 },
1007 ],
1008 },
1009 State {
1010 state_type: StateType::NetHsm,
1011 users: vec![
1012 UserState{
1013 name: "operator1".parse()?,
1014 role: UserRole::Metrics,
1015 tags: Vec::new(),
1016 },
1017 UserState{
1018 name: "admin".parse()?,
1019 role: UserRole::Administrator,
1020 tags: Vec::new(),
1021 },
1022 ],
1023 keys: vec![
1024 KeyState{
1025 name: "key1".parse()?,
1026 namespace: None,
1027 tags: vec!["tag1".to_string()],
1028 key_type: KeyType::Curve25519,
1029 mechanisms: vec![KeyMechanism::EdDsaSignature],
1030 key_cert_state: KeyCertificateState::KeyContext(CryptographicKeyContext::Raw)
1031 },
1032 ],
1033 },
1034 r#"NetHSM backend error:
1035Errors occurred when comparing states:
1036User mismatch:
1037Signstar configuration (A) => operator1 (role: Operator; tags: tag1)
1038NetHSM (B) => operator1 (role: Metrics)
1039Key mismatch:
1040Signstar configuration (A) => key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: OpenPGP (Version: 4; User IDs: "Foobar McFooface <foobar@mcfooface.org>"))
1041NetHSM (B) => key1 (tags: tag1; type: Curve25519; mechanisms: EdDsaSignature; context: Raw)
1042"#,
1043 )]
1044 fn state_compare_fails(
1045 #[case] state_a: State,
1046 #[case] state_b: State,
1047 #[case] expected: &str,
1048 ) -> TestResult {
1049 setup_logging(LevelFilter::Debug)?;
1050
1051 let compare_result = state_a.compare(&state_b);
1052
1053 match compare_result {
1054 Ok(_) => panic!("Comparison should have failed but succeeded"),
1055 Err(error) => assert_eq!(error.to_string(), expected),
1056 }
1057
1058 Ok(())
1059 }
1060}