1use std::{
25 collections::{HashMap, HashSet},
26 future::Future,
27 pin::Pin,
28 sync::Arc,
29 task::Poll,
30};
31
32use dashmap::DashMap;
33use tower::Service;
34#[cfg(feature = "trace2e_tracing")]
35use tracing::info;
36
37use crate::traceability::{
38 api::{ComplianceRequest, ComplianceResponse, ConsentRequest, ConsentResponse},
39 core::consent::ConsentService,
40 error::TraceabilityError,
41 naming::Resource,
42};
43
44#[derive(Default, PartialEq, Debug, Clone, Copy, Eq)]
56pub enum ConfidentialityPolicy {
57 Secret,
59 #[default]
61 Public,
62}
63
64#[derive(Default, PartialEq, Debug, Clone, Eq, Copy)]
81pub enum DeletionPolicy {
82 #[default]
84 NotDeleted,
85 Pending,
87 Deleted,
89}
90
91impl From<bool> for DeletionPolicy {
92 fn from(deleted: bool) -> Self {
93 if deleted { DeletionPolicy::Deleted } else { DeletionPolicy::NotDeleted }
94 }
95}
96
97#[derive(Clone, Debug, Eq, PartialEq)]
119pub struct Policy {
120 confidentiality: ConfidentialityPolicy,
122 integrity: u32,
124 deleted: DeletionPolicy,
126 consent: bool,
128}
129
130impl Default for Policy {
131 fn default() -> Self {
132 Policy {
133 confidentiality: ConfidentialityPolicy::Public,
134 integrity: 0,
135 deleted: DeletionPolicy::NotDeleted,
136 consent: false,
137 }
138 }
139}
140
141impl Policy {
142 pub fn new(
151 confidentiality: ConfidentialityPolicy,
152 integrity: u32,
153 deleted: DeletionPolicy,
154 consent: bool,
155 ) -> Self {
156 Self { confidentiality, integrity, deleted, consent }
157 }
158
159 pub fn is_confidential(&self) -> bool {
164 self.confidentiality == ConfidentialityPolicy::Secret
165 }
166
167 pub fn is_deleted(&self) -> bool {
171 self.deleted != DeletionPolicy::NotDeleted
172 }
173
174 pub fn is_pending_deletion(&self) -> bool {
179 self.deleted == DeletionPolicy::Pending
180 }
181
182 pub fn get_integrity(&self) -> u32 {
187 self.integrity
188 }
189
190 pub fn get_consent(&self) -> bool {
194 self.consent
195 }
196
197 pub fn with_consent(&mut self, consent: bool) -> ComplianceResponse {
202 if !self.is_deleted() {
203 self.consent = consent;
204 ComplianceResponse::PolicyUpdated
205 } else {
206 ComplianceResponse::PolicyNotUpdated
207 }
208 }
209
210 pub fn with_integrity(&mut self, integrity: u32) -> ComplianceResponse {
219 if !self.is_deleted() {
220 self.integrity = integrity;
221 ComplianceResponse::PolicyUpdated
222 } else {
223 ComplianceResponse::PolicyNotUpdated
224 }
225 }
226
227 pub fn with_confidentiality(
236 &mut self,
237 confidentiality: ConfidentialityPolicy,
238 ) -> ComplianceResponse {
239 if !self.is_deleted() {
240 self.confidentiality = confidentiality;
241 ComplianceResponse::PolicyUpdated
242 } else {
243 ComplianceResponse::PolicyNotUpdated
244 }
245 }
246
247 pub fn deleted(&mut self) -> ComplianceResponse {
255 if !self.is_deleted() {
256 self.deleted = DeletionPolicy::Pending;
257 ComplianceResponse::PolicyUpdated
258 } else {
259 ComplianceResponse::PolicyNotUpdated
260 }
261 }
262
263 pub fn deletion_enforced(&mut self) -> ComplianceResponse {
271 if self.is_pending_deletion() {
272 self.deleted = DeletionPolicy::Deleted;
273 ComplianceResponse::PolicyUpdated
274 } else {
275 ComplianceResponse::PolicyNotUpdated
276 }
277 }
278}
279
280#[derive(Clone, Debug)]
322pub struct ComplianceService<C = ConsentService> {
323 cache_mode: bool,
325 policies: Arc<DashMap<Resource, Policy>>,
327 consent: C,
329}
330
331impl Default for ComplianceService {
332 fn default() -> Self {
333 Self {
334 cache_mode: false,
335 policies: Arc::new(DashMap::new()),
336 consent: ConsentService::default(),
337 }
338 }
339}
340
341impl ComplianceService<ConsentService> {
342 pub fn new_with_consent(consent: ConsentService) -> Self {
343 Self { cache_mode: false, policies: Arc::new(DashMap::new()), consent }
344 }
345
346 #[cfg(test)]
351 fn init_cache() -> Self {
352 Self {
353 cache_mode: true,
354 policies: Arc::new(DashMap::new()),
355 consent: ConsentService::default(),
356 }
357 }
358
359 async fn eval_policies(
403 &self,
404 source_policies: HashMap<String, HashMap<Resource, Policy>>,
405 destination: Resource,
406 ) -> Result<ComplianceResponse, TraceabilityError> {
407 let destination_policy = self.get_policy(&destination)?;
408
409 for (node, source_policy_batch) in source_policies {
412 for (source, source_policy) in source_policy_batch {
413 if source_policy.get_consent() {
414 let mut consent = self.consent.clone();
418
419 let _ = consent
424 .call(ConsentRequest::RequestConsent {
425 source: source.clone(),
426 destination: (Some(node.clone()), destination.clone()),
427 })
428 .await
429 .map_err(|_| TraceabilityError::InternalTrace2eError)
430 .map(|consent| match consent {
431 ConsentResponse::Consent(true) => Ok(()),
432 ConsentResponse::Consent(false) => {
433 Err(TraceabilityError::DirectPolicyViolation)
434 }
435 _ => Err(TraceabilityError::InternalTrace2eError),
436 })?;
437 }
438 if source_policy.is_deleted() || destination_policy.is_deleted() {
440 #[cfg(feature = "trace2e_tracing")]
441 info!(
442 "[compliance] EvalPolicies: source_policy.deleted: {:?}, destination_policy.deleted: {:?}",
443 source_policy.is_deleted(),
444 destination_policy.is_deleted()
445 );
446 #[cfg(feature = "enforcement_mocking")]
447 if source_policy.is_pending_deletion() {
448 #[cfg(feature = "trace2e_tracing")]
449 info!("[compliance] Enforcing deletion policy for source");
450 }
451 #[cfg(feature = "enforcement_mocking")]
452 if destination_policy.is_pending_deletion() {
453 #[cfg(feature = "trace2e_tracing")]
454 info!("[compliance] Enforcing deletion policy for destination");
455 }
456
457 return Err(TraceabilityError::DirectPolicyViolation);
458 }
459
460 if source_policy.integrity < destination_policy.integrity {
463 return Err(TraceabilityError::DirectPolicyViolation);
464 }
465
466 if source_policy.confidentiality == ConfidentialityPolicy::Secret
468 && destination_policy.confidentiality == ConfidentialityPolicy::Public
469 {
470 return Err(TraceabilityError::DirectPolicyViolation);
471 }
472 }
473 }
474
475 Ok(ComplianceResponse::Grant)
476 }
477}
478
479impl ComplianceService {
480 fn get_policy(&self, resource: &Resource) -> Result<Policy, TraceabilityError> {
495 self.policies.get(resource).map(|p| p.to_owned()).map_or(
496 if self.cache_mode {
497 Err(TraceabilityError::PolicyNotFound(resource.to_owned()))
498 } else {
499 Ok(Policy::default())
500 },
501 Ok,
502 )
503 }
504
505 fn get_policies(
526 &self,
527 resources: HashSet<Resource>,
528 ) -> Result<HashMap<Resource, Policy>, TraceabilityError> {
529 let mut policies_set = HashMap::new();
530 for resource in resources {
531 if resource.is_stream().is_none() {
533 if let Ok(policy) = self.get_policy(&resource) {
534 policies_set.insert(resource, policy);
535 } else if !self.cache_mode {
536 policies_set.insert(resource, Policy::default());
537 } else {
538 return Err(TraceabilityError::PolicyNotFound(resource));
539 }
540 }
541 }
542 Ok(policies_set)
543 }
544
545 fn set_policy(&self, resource: Resource, policy: Policy) -> ComplianceResponse {
560 if self.policies.get(&resource).is_some_and(|policy| policy.is_deleted()) {
562 return ComplianceResponse::PolicyNotUpdated;
563 }
564 self.policies.insert(resource, policy);
565 ComplianceResponse::PolicyUpdated
566 }
567
568 fn set_confidentiality(
578 &self,
579 resource: Resource,
580 confidentiality: ConfidentialityPolicy,
581 ) -> ComplianceResponse {
582 let mut response = ComplianceResponse::PolicyNotUpdated;
583 self.policies
584 .entry(resource)
585 .and_modify(|policy| {
586 response = policy.with_confidentiality(confidentiality);
587 })
588 .or_insert_with(|| {
589 let mut policy = Policy::default();
590 response = policy.with_confidentiality(confidentiality);
591 policy
592 });
593 response
594 }
595
596 fn set_integrity(&self, resource: Resource, integrity: u32) -> ComplianceResponse {
606 let mut response = ComplianceResponse::PolicyNotUpdated;
607 self.policies
608 .entry(resource)
609 .and_modify(|policy| {
610 response = policy.with_integrity(integrity);
611 })
612 .or_insert_with(|| {
613 let mut policy = Policy::default();
614 response = policy.with_integrity(integrity);
615 policy
616 });
617 response
618 }
619
620 fn set_deleted(&self, resource: Resource) -> ComplianceResponse {
629 let mut response = ComplianceResponse::PolicyNotUpdated;
630 self.policies
631 .entry(resource)
632 .and_modify(|policy| {
633 response = policy.deleted();
634 })
635 .or_insert_with(|| {
636 let mut policy = Policy::default();
637 response = policy.deleted();
638 policy
639 });
640 response
641 }
642
643 fn set_consent(&self, resource: Resource, consent: bool) -> ComplianceResponse {
653 let mut response = ComplianceResponse::PolicyNotUpdated;
654 self.policies
655 .entry(resource)
656 .and_modify(|policy| {
657 response = policy.with_consent(consent);
658 })
659 .or_insert_with(|| {
660 let mut policy = Policy::default();
661 response = policy.with_consent(consent);
662 policy
663 });
664 response
665 }
666}
667
668impl Service<ComplianceRequest> for ComplianceService<ConsentService> {
669 type Response = ComplianceResponse;
670 type Error = TraceabilityError;
671 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
672
673 fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
674 Poll::Ready(Ok(()))
675 }
676
677 fn call(&mut self, request: ComplianceRequest) -> Self::Future {
678 let this = self.clone();
679 Box::pin(async move {
680 match request {
681 ComplianceRequest::EvalPolicies { source_policies, destination } => {
682 #[cfg(feature = "trace2e_tracing")]
683 info!(
684 "[compliance] CheckCompliance: source_policies: {:?}, destination: {:?}",
685 source_policies, destination
686 );
687 this.eval_policies(source_policies, destination).await
688 }
689 ComplianceRequest::GetPolicy(resource) => {
690 #[cfg(feature = "trace2e_tracing")]
691 info!("[compliance] GetPolicy: resource: {:?}", resource);
692 Ok(ComplianceResponse::Policy(this.get_policy(&resource)?))
693 }
694 ComplianceRequest::GetPolicies(resources) => {
695 #[cfg(feature = "trace2e_tracing")]
696 info!("[compliance] GetPolicies: resources: {:?}", resources);
697 Ok(ComplianceResponse::Policies(this.get_policies(resources)?))
698 }
699 ComplianceRequest::SetPolicy { resource, policy } => {
700 #[cfg(feature = "trace2e_tracing")]
701 info!("[compliance] SetPolicy: resource: {:?}, policy: {:?}", resource, policy);
702 Ok(this.set_policy(resource, policy))
703 }
704 ComplianceRequest::SetConfidentiality { resource, confidentiality } => {
705 #[cfg(feature = "trace2e_tracing")]
706 info!(
707 "[compliance] SetConfidentiality: resource: {:?}, confidentiality: {:?}",
708 resource, confidentiality
709 );
710 Ok(this.set_confidentiality(resource, confidentiality))
711 }
712 ComplianceRequest::SetIntegrity { resource, integrity } => {
713 #[cfg(feature = "trace2e_tracing")]
714 info!(
715 "[compliance] SetIntegrity: resource: {:?}, integrity: {:?}",
716 resource, integrity
717 );
718 Ok(this.set_integrity(resource, integrity))
719 }
720 ComplianceRequest::SetDeleted(resource) => {
721 #[cfg(feature = "trace2e_tracing")]
722 info!("[compliance] SetDeleted: resource: {:?}", resource);
723 Ok(this.set_deleted(resource))
724 }
725 ComplianceRequest::SetConsent { resource, consent } => {
726 #[cfg(feature = "trace2e_tracing")]
727 info!(
728 "[compliance] SetConsent: resource: {:?}, consent: {:?}",
729 resource, consent
730 );
731 Ok(this.set_consent(resource, consent))
732 }
733 }
734 })
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use crate::traceability::naming::Resource;
742
743 fn create_public_policy(integrity: u32) -> Policy {
745 Policy::new(ConfidentialityPolicy::Public, integrity, DeletionPolicy::NotDeleted, false)
746 }
747
748 fn create_secret_policy(integrity: u32) -> Policy {
749 Policy::new(ConfidentialityPolicy::Secret, integrity, DeletionPolicy::NotDeleted, false)
750 }
751
752 fn create_deleted_policy(integrity: u32) -> Policy {
753 Policy::new(ConfidentialityPolicy::Public, integrity, DeletionPolicy::Pending, false)
754 }
755
756 fn init_tracing() {
757 #[cfg(feature = "trace2e_tracing")]
758 crate::trace2e_tracing::init();
759 }
760
761 #[tokio::test]
762 async fn unit_compliance_set_policy_basic() {
763 init_tracing();
764 let compliance = ComplianceService::default();
765 let process = Resource::new_process_mock(0);
766
767 let policy = create_secret_policy(5);
768
769 assert_eq!(
771 compliance.set_policy(process.clone(), policy),
772 ComplianceResponse::PolicyUpdated
773 );
774
775 let new_policy = create_secret_policy(3);
776
777 assert_eq!(compliance.set_policy(process, new_policy), ComplianceResponse::PolicyUpdated);
779 }
780
781 #[tokio::test]
782 async fn unit_compliance_policy_evaluation_scenarios() {
783 init_tracing();
784 let compliance = ComplianceService::default();
785 let test_cases = [
787 (create_public_policy(5), create_public_policy(3), true, "integrity pass: 5 >= 3"),
789 (create_public_policy(3), create_public_policy(5), false, "integrity fail: 3 < 5"),
790 (
791 create_secret_policy(5),
792 create_secret_policy(3),
793 true,
794 "confidentiality pass: secret -> secret",
795 ),
796 (
797 create_secret_policy(5),
798 create_public_policy(3),
799 false,
800 "confidentiality fail: secret -> public",
801 ),
802 (
803 create_public_policy(5),
804 create_secret_policy(3),
805 true,
806 "confidentiality pass: public -> secret",
807 ),
808 ];
809 let mock_file = Resource::new_file("/tmp/dest".to_string());
810
811 for (source_policy, dest_policy, should_pass, description) in test_cases {
812 compliance.set_policy(mock_file.clone(), dest_policy);
813 let result = compliance
814 .eval_policies(
815 HashMap::from([(
816 String::new(),
817 HashMap::from([(Resource::new_process_mock(0), source_policy)]),
818 )]),
819 mock_file.clone(),
820 )
821 .await;
822
823 if should_pass {
824 assert!(
825 result.is_ok_and(|r| r == ComplianceResponse::Grant),
826 "Test failed: {description}"
827 );
828 } else {
829 assert!(
830 result.is_err_and(|e| e == TraceabilityError::DirectPolicyViolation),
831 "Test failed: {description}"
832 );
833 }
834 }
835 }
836
837 #[tokio::test]
838 async fn unit_compliance_default_policies() {
839 init_tracing();
840 let compliance = ComplianceService::default();
841 let mock_file = Resource::new_file("/tmp/dest".to_string());
842 assert!(
844 compliance
845 .eval_policies(
846 HashMap::from([
847 (
848 String::new(),
849 HashMap::from([
850 (Resource::new_process_mock(0), Policy::default()),
851 (Resource::new_process_mock(1), Policy::default())
852 ])
853 ),
854 (
855 "10.0.0.1".to_string(),
856 HashMap::from([(Resource::new_process_mock(0), Policy::default())])
857 )
858 ]),
859 mock_file.clone()
860 )
861 .await
862 .is_ok_and(|r| r == ComplianceResponse::Grant)
863 );
864
865 let dest_policy = create_public_policy(2);
867 compliance.set_policy(mock_file.clone(), dest_policy);
868 assert!(
869 compliance
870 .eval_policies(
871 HashMap::from([
872 (
873 String::new(),
874 HashMap::from([(Resource::new_process_mock(0), Policy::default())])
875 ), (
877 "10.0.0.1".to_string(),
878 HashMap::from([(Resource::new_process_mock(0), Policy::default())])
879 )
880 ]),
881 mock_file )
883 .await
884 .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
885 );
886 }
887
888 #[tokio::test]
889 async fn unit_compliance_service_eval_policies_requests() {
890 init_tracing();
891 let mut compliance = ComplianceService::default();
892
893 let file = Resource::new_file("/tmp/dest".to_string());
894 let policy = create_public_policy(3);
895 compliance.set_policy(file.clone(), policy);
896
897 let grant_request = ComplianceRequest::EvalPolicies {
899 source_policies: HashMap::from([(
900 String::new(),
901 HashMap::from([(Resource::new_process_mock(0), create_public_policy(5))]),
902 )]),
903 destination: file.clone(),
904 };
905 assert_eq!(compliance.call(grant_request).await.unwrap(), ComplianceResponse::Grant);
906
907 let deny_request = ComplianceRequest::EvalPolicies {
909 source_policies: HashMap::from([(
910 String::new(),
911 HashMap::from([(Resource::new_process_mock(0), create_secret_policy(5))]),
912 )]),
913 destination: file.clone(),
914 };
915 assert_eq!(
916 compliance.call(deny_request).await.unwrap_err(),
917 TraceabilityError::DirectPolicyViolation
918 );
919 }
920
921 #[tokio::test]
922 async fn unit_compliance_service_complex_policy_scenario() {
923 init_tracing();
924 let mut compliance = ComplianceService::default();
925
926 let file = Resource::new_file("/tmp/dest".to_string());
927 let high_policy = create_secret_policy(10);
928 let medium_policy = create_public_policy(5);
929 let low_policy = create_public_policy(1);
930
931 compliance.set_policy(file.clone(), medium_policy.clone());
932
933 let request1 = ComplianceRequest::EvalPolicies {
935 source_policies: HashMap::from([
936 (
937 String::new(),
938 HashMap::from([(Resource::new_process_mock(0), Policy::default())]),
939 ),
940 (
941 "10.0.0.1".to_string(),
942 HashMap::from([(Resource::new_process_mock(0), high_policy.clone())]),
943 ),
944 ]),
945 destination: file.clone(),
946 };
947 assert_eq!(
948 compliance.call(request1).await.unwrap_err(),
949 TraceabilityError::DirectPolicyViolation
950 ); compliance.set_policy(file.clone(), low_policy.clone());
954 let request2 = ComplianceRequest::EvalPolicies {
955 source_policies: HashMap::from([(
956 String::new(),
957 HashMap::from([(Resource::new_process_mock(0), medium_policy)]),
958 )]),
959 destination: file.clone(),
960 };
961 assert_eq!(compliance.call(request2).await.unwrap(), ComplianceResponse::Grant);
962
963 compliance.set_policy(file.clone(), high_policy.clone());
965 let request3 = ComplianceRequest::EvalPolicies {
966 source_policies: HashMap::from([(
967 String::new(),
968 HashMap::from([(Resource::new_process_mock(0), low_policy)]),
969 )]),
970 destination: file,
971 };
972 assert_eq!(
973 compliance.call(request3).await.unwrap_err(),
974 TraceabilityError::DirectPolicyViolation
975 );
976 }
977
978 #[tokio::test]
979 async fn unit_compliance_get_policies_empty() {
980 #[cfg(feature = "trace2e_tracing")]
981 crate::trace2e_tracing::init();
982 let compliance = ComplianceService::default();
983 let process = Resource::new_process_mock(0);
984 let file = Resource::new_file("/tmp/test".to_string());
985
986 assert_eq!(
987 compliance.get_policies(HashSet::from([process.clone(), file.clone()])).unwrap(),
988 HashMap::from([(process, Policy::default()), (file, Policy::default())])
989 );
990 }
991
992 #[tokio::test]
993 async fn unit_compliance_get_policies_scenarios() {
994 init_tracing();
995 let compliance = ComplianceService::default();
996
997 let process = Resource::new_process_mock(0);
999 let file = Resource::new_file("/tmp/test".to_string());
1000
1001 let process_policy = create_secret_policy(7);
1003 let file_policy = create_public_policy(3);
1004
1005 compliance.set_policy(process.clone(), process_policy.clone());
1007 assert_eq!(
1008 compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1009 HashMap::from([(process.clone(), process_policy.clone())])
1010 );
1011
1012 compliance.set_policy(file.clone(), file_policy.clone());
1014 assert_eq!(
1015 compliance.get_policies(HashSet::from([process.clone(), file.clone()])).unwrap(),
1016 HashMap::from([(process.clone(), process_policy.clone()), (file.clone(), file_policy)])
1017 );
1018
1019 let new_file = Resource::new_file("/tmp/new.txt".to_string());
1021 assert_eq!(
1022 compliance.get_policies(HashSet::from([process.clone(), new_file.clone()])).unwrap(),
1023 HashMap::from([(process, process_policy), (new_file, Policy::default())])
1024 );
1025 }
1026
1027 #[tokio::test]
1028 async fn unit_compliance_get_policies_edge_cases() {
1029 init_tracing();
1030 let compliance = ComplianceService::default();
1031 let process = Resource::new_process_mock(0);
1032
1033 assert_eq!(compliance.get_policies(HashSet::new()).unwrap(), HashMap::new());
1035
1036 let initial_policy = create_public_policy(2);
1038 let updated_policy = create_secret_policy(9);
1039
1040 compliance.set_policy(process.clone(), initial_policy.clone());
1041 assert_eq!(
1042 compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1043 HashMap::from([(process.clone(), initial_policy.clone())])
1044 );
1045
1046 compliance.set_policy(process.clone(), updated_policy.clone());
1047 assert_eq!(
1048 compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1049 HashMap::from([(process, updated_policy)])
1050 );
1051 }
1052
1053 #[tokio::test]
1054 async fn unit_compliance_deleted_policy_behavior() {
1055 init_tracing();
1056 let compliance = ComplianceService::default();
1057 let process = Resource::new_process_mock(0);
1058 let mock_file = Resource::new_file("/tmp/dest".to_string());
1059
1060 let deleted_policy = create_deleted_policy(5);
1062 compliance.set_policy(process.clone(), deleted_policy.clone());
1063
1064 assert_eq!(
1066 compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1067 HashMap::from([(process.clone(), deleted_policy.clone())])
1068 );
1069
1070 assert_eq!(
1072 compliance.set_policy(process.clone(), Policy::default()),
1073 ComplianceResponse::PolicyNotUpdated
1074 );
1075
1076 assert_eq!(
1078 compliance.get_policies(HashSet::from([process.clone()])).unwrap(),
1079 HashMap::from([(process.clone(), deleted_policy.clone())])
1080 );
1081
1082 assert!(
1084 compliance
1085 .eval_policies(
1086 HashMap::from([(
1087 String::new(),
1088 HashMap::from([
1089 (Resource::new_process_mock(0), deleted_policy.clone()),
1090 (Resource::new_process_mock(1), Policy::default())
1091 ])
1092 )]),
1093 mock_file.clone()
1094 )
1095 .await
1096 .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
1097 );
1098
1099 assert!(
1100 compliance
1101 .eval_policies(
1102 HashMap::from([(
1103 String::new(),
1104 HashMap::from([(Resource::new_process_mock(0), Policy::default())])
1105 )]),
1106 process.clone()
1107 )
1108 .await
1109 .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
1110 );
1111 }
1112
1113 #[tokio::test]
1114 async fn unit_compliance_cache_mode_policy_not_found() {
1115 #[cfg(feature = "trace2e_tracing")]
1116 crate::trace2e_tracing::init();
1117
1118 let cache_policy_map = ComplianceService::init_cache();
1119 let process = Resource::new_process_mock(0);
1120
1121 assert!(
1123 cache_policy_map.get_policies(HashSet::from([process.clone()])).is_err_and(
1124 |e| matches!(e, TraceabilityError::PolicyNotFound(res) if res == process)
1125 )
1126 );
1127 }
1128
1129 #[tokio::test]
1130 async fn unit_compliance_normal_mode_vs_cache_mode() {
1131 #[cfg(feature = "trace2e_tracing")]
1132 crate::trace2e_tracing::init();
1133
1134 let normal_policy_map = ComplianceService::default();
1135 let cache_policy_map = ComplianceService::init_cache();
1136 let process = Resource::new_process_mock(0);
1137
1138 assert_eq!(
1140 normal_policy_map.get_policies(HashSet::from([process.clone()])).unwrap(),
1141 HashMap::from([(process.clone(), Policy::default())])
1142 );
1143
1144 assert!(
1146 cache_policy_map.get_policies(HashSet::from([process.clone()])).is_err_and(
1147 |e| matches!(e, TraceabilityError::PolicyNotFound(res) if res == process)
1148 )
1149 );
1150 }
1151
1152 #[tokio::test]
1153 async fn unit_compliance_cache_mode_with_existing_policies() {
1154 #[cfg(feature = "trace2e_tracing")]
1155 crate::trace2e_tracing::init();
1156
1157 let cache_policy_map = ComplianceService::init_cache();
1158 let process = Resource::new_process_mock(0);
1159
1160 let policy =
1161 Policy::new(ConfidentialityPolicy::Secret, 7, DeletionPolicy::NotDeleted, true);
1162
1163 cache_policy_map.set_policy(process.clone(), policy.clone());
1165
1166 assert_eq!(
1168 cache_policy_map.get_policies(HashSet::from([process.clone()])).unwrap(),
1169 HashMap::from([(process, policy)])
1170 );
1171 }
1172
1173 #[tokio::test]
1174 async fn unit_compliance_cache_mode_mixed_resources() {
1175 #[cfg(feature = "trace2e_tracing")]
1176 crate::trace2e_tracing::init();
1177
1178 let cache_policy_map = ComplianceService::init_cache();
1179 let process1 = Resource::new_process_mock(1);
1180 let process2 = Resource::new_process_mock(2);
1181
1182 let policy1 = Policy {
1183 confidentiality: ConfidentialityPolicy::Public,
1184 integrity: 3,
1185 deleted: DeletionPolicy::NotDeleted,
1186 consent: true,
1187 };
1188
1189 cache_policy_map.set_policy(process1.clone(), policy1);
1191
1192 assert!(
1194 cache_policy_map.get_policies(HashSet::from([process1, process2.clone()])).is_err_and(
1195 |e| matches!(e, TraceabilityError::PolicyNotFound(res) if res == process2)
1196 )
1197 );
1198 }
1199
1200 #[tokio::test]
1201 async fn unit_compliance_cache_mode_empty_request() {
1202 #[cfg(feature = "trace2e_tracing")]
1203 crate::trace2e_tracing::init();
1204
1205 let cache_policy_map = ComplianceService::init_cache();
1206
1207 assert_eq!(cache_policy_map.get_policies(HashSet::new()).unwrap(), HashMap::new());
1209 }
1210
1211 #[tokio::test]
1212 async fn unit_deletion_policy_state_transitions() {
1213 init_tracing();
1214
1215 let mut policy = Policy::default();
1217 assert!(!policy.is_deleted());
1218 assert!(!policy.is_pending_deletion());
1219
1220 policy.deleted();
1222 assert!(policy.is_deleted()); assert!(policy.is_pending_deletion());
1224
1225 policy.deleted();
1227 assert!(policy.is_deleted());
1228 assert!(policy.is_pending_deletion());
1229
1230 policy.deletion_enforced();
1232 assert!(policy.is_deleted());
1233 assert!(!policy.is_pending_deletion());
1234
1235 policy.deletion_enforced();
1237 assert!(policy.is_deleted());
1238 assert!(!policy.is_pending_deletion());
1239
1240 assert_eq!(DeletionPolicy::from(true), DeletionPolicy::Deleted);
1242 assert_eq!(DeletionPolicy::from(false), DeletionPolicy::NotDeleted);
1243 }
1244
1245 #[tokio::test]
1246 async fn unit_compliance_service_deletion_policy_workflow() {
1247 init_tracing();
1248 let mut compliance = ComplianceService::default();
1249
1250 let process = Resource::new_process_mock(0);
1251 let file = Resource::new_file("/tmp/sensitive.txt".to_string());
1252
1253 let process_policy = create_secret_policy(5);
1255 let file_policy = create_public_policy(3);
1256
1257 let set_process_request = ComplianceRequest::SetPolicy {
1258 resource: process.clone(),
1259 policy: process_policy.clone(),
1260 };
1261 assert_eq!(
1262 compliance.call(set_process_request).await.unwrap(),
1263 ComplianceResponse::PolicyUpdated
1264 );
1265
1266 let set_file_request =
1267 ComplianceRequest::SetPolicy { resource: file.clone(), policy: file_policy.clone() };
1268 assert_eq!(
1269 compliance.call(set_file_request).await.unwrap(),
1270 ComplianceResponse::PolicyUpdated
1271 );
1272
1273 let get_process_request = ComplianceRequest::GetPolicy(process.clone());
1275 if let ComplianceResponse::Policy(retrieved_policy) =
1276 compliance.call(get_process_request).await.unwrap()
1277 {
1278 assert_eq!(retrieved_policy, process_policy);
1279 assert!(!retrieved_policy.is_deleted());
1280 } else {
1281 panic!("Expected Policy response");
1282 }
1283
1284 let mut pending_deletion_policy = create_secret_policy(7);
1286 pending_deletion_policy.deleted();
1287 assert!(pending_deletion_policy.is_pending_deletion());
1288
1289 let set_pending_request = ComplianceRequest::SetPolicy {
1290 resource: file.clone(),
1291 policy: pending_deletion_policy.clone(),
1292 };
1293 assert_eq!(
1294 compliance.call(set_pending_request).await.unwrap(),
1295 ComplianceResponse::PolicyUpdated
1296 );
1297
1298 let get_file_request = ComplianceRequest::GetPolicy(file.clone());
1300 if let ComplianceResponse::Policy(retrieved_policy) =
1301 compliance.call(get_file_request).await.unwrap()
1302 {
1303 assert_eq!(retrieved_policy, pending_deletion_policy);
1304 assert!(retrieved_policy.is_deleted());
1305 assert!(retrieved_policy.is_pending_deletion());
1306 } else {
1307 panic!("Expected Policy response");
1308 }
1309
1310 let new_policy = create_public_policy(1);
1312 let update_pending_request =
1313 ComplianceRequest::SetPolicy { resource: file.clone(), policy: new_policy };
1314 assert_eq!(
1315 compliance.call(update_pending_request).await.unwrap(),
1316 ComplianceResponse::PolicyNotUpdated
1317 );
1318
1319 let mut deleted_policy = create_public_policy(2);
1321 deleted_policy.deleted();
1322 deleted_policy.deletion_enforced();
1323 assert!(deleted_policy.is_deleted());
1324 assert!(!deleted_policy.is_pending_deletion());
1325
1326 let new_resource = Resource::new_process_mock(1);
1327 let set_deleted_request = ComplianceRequest::SetPolicy {
1328 resource: new_resource.clone(),
1329 policy: deleted_policy.clone(),
1330 };
1331 assert_eq!(
1332 compliance.call(set_deleted_request).await.unwrap(),
1333 ComplianceResponse::PolicyUpdated
1334 );
1335
1336 let another_policy = create_secret_policy(10);
1338 let update_deleted_request =
1339 ComplianceRequest::SetPolicy { resource: new_resource.clone(), policy: another_policy };
1340 assert_eq!(
1341 compliance.call(update_deleted_request).await.unwrap(),
1342 ComplianceResponse::PolicyNotUpdated
1343 );
1344
1345 let eval_with_deleted_source = ComplianceRequest::EvalPolicies {
1347 source_policies: HashMap::from([(
1348 String::new(),
1349 HashMap::from([(new_resource, deleted_policy)]),
1350 )]),
1351 destination: Resource::new_file("/tmp/dest".to_string()),
1352 };
1353 assert_eq!(
1354 compliance.call(eval_with_deleted_source).await.unwrap_err(),
1355 TraceabilityError::DirectPolicyViolation
1356 );
1357
1358 let eval_with_deleted_dest = ComplianceRequest::EvalPolicies {
1360 source_policies: HashMap::from([(
1361 String::new(),
1362 HashMap::from([(process, process_policy)]),
1363 )]),
1364 destination: Resource::new_file("/tmp/dest".to_string()),
1365 };
1366 assert_eq!(
1367 compliance.call(eval_with_deleted_dest).await.unwrap_err(),
1368 TraceabilityError::DirectPolicyViolation
1369 );
1370 }
1371}