1use std::{
25 collections::{HashMap, HashSet},
26 future::Future,
27 pin::Pin,
28 sync::Arc,
29 task::Poll,
30};
31
32use crate::traceability::infrastructure::naming::DisplayableResource;
33use dashmap::DashMap;
34use tokio::task::JoinSet;
35use tower::Service;
36use tracing::info;
37
38use crate::traceability::{
39 api::types::{ComplianceRequest, ComplianceResponse},
40 error::TraceabilityError,
41 infrastructure::naming::{LocalizedResource, Resource},
42 services::consent::{ConsentRequest, ConsentResponse, ConsentService},
43};
44
45#[derive(Default, PartialEq, Debug, Clone, Copy, Eq)]
57pub enum ConfidentialityPolicy {
58 Secret,
60 #[default]
62 Public,
63}
64
65#[derive(Default, PartialEq, Debug, Clone, Eq, Copy)]
82pub enum DeletionPolicy {
83 #[default]
85 NotDeleted,
86 Pending,
88 Deleted,
90}
91
92impl From<bool> for DeletionPolicy {
93 fn from(deleted: bool) -> Self {
94 if deleted { DeletionPolicy::Deleted } else { DeletionPolicy::NotDeleted }
95 }
96}
97
98#[derive(Clone, Debug, Eq, PartialEq)]
120pub struct Policy {
121 confidentiality: ConfidentialityPolicy,
123 integrity: u32,
125 deleted: DeletionPolicy,
127 consent: bool,
129}
130
131impl Default for Policy {
132 fn default() -> Self {
133 Policy {
134 confidentiality: ConfidentialityPolicy::Public,
135 integrity: 0,
136 deleted: DeletionPolicy::NotDeleted,
137 consent: false,
138 }
139 }
140}
141
142impl Policy {
143 pub fn new(
152 confidentiality: ConfidentialityPolicy,
153 integrity: u32,
154 deleted: DeletionPolicy,
155 consent: bool,
156 ) -> Self {
157 Self { confidentiality, integrity, deleted, consent }
158 }
159
160 pub fn is_confidential(&self) -> bool {
165 self.confidentiality == ConfidentialityPolicy::Secret
166 }
167
168 pub fn is_deleted(&self) -> bool {
172 self.deleted != DeletionPolicy::NotDeleted
173 }
174
175 pub fn is_pending_deletion(&self) -> bool {
180 self.deleted == DeletionPolicy::Pending
181 }
182
183 pub fn get_integrity(&self) -> u32 {
188 self.integrity
189 }
190
191 pub fn get_consent(&self) -> bool {
195 self.consent
196 }
197
198 pub fn with_consent(&mut self, consent: bool) -> ComplianceResponse {
203 if !self.is_deleted() {
204 self.consent = consent;
205 ComplianceResponse::PolicyUpdated
206 } else {
207 ComplianceResponse::PolicyNotUpdated
208 }
209 }
210
211 pub fn with_integrity(&mut self, integrity: u32) -> ComplianceResponse {
220 if !self.is_deleted() {
221 self.integrity = integrity;
222 ComplianceResponse::PolicyUpdated
223 } else {
224 ComplianceResponse::PolicyNotUpdated
225 }
226 }
227
228 pub fn with_confidentiality(
237 &mut self,
238 confidentiality: ConfidentialityPolicy,
239 ) -> ComplianceResponse {
240 if !self.is_deleted() {
241 self.confidentiality = confidentiality;
242 ComplianceResponse::PolicyUpdated
243 } else {
244 ComplianceResponse::PolicyNotUpdated
245 }
246 }
247
248 pub fn deleted(&mut self) -> ComplianceResponse {
256 if !self.is_deleted() {
257 self.deleted = DeletionPolicy::Pending;
258 ComplianceResponse::PolicyUpdated
259 } else {
260 ComplianceResponse::PolicyNotUpdated
261 }
262 }
263
264 pub fn deletion_enforced(&mut self) -> ComplianceResponse {
272 if self.is_pending_deletion() {
273 self.deleted = DeletionPolicy::Deleted;
274 ComplianceResponse::PolicyUpdated
275 } else {
276 ComplianceResponse::PolicyNotUpdated
277 }
278 }
279}
280
281#[derive(Clone, Debug)]
317pub struct ComplianceService<C = ConsentService> {
318 node_id: String,
320 policies: Arc<DashMap<Resource, Policy>>,
322 consent: C,
324}
325
326impl Default for ComplianceService {
327 fn default() -> Self {
328 Self {
329 node_id: String::new(),
330 policies: Arc::new(DashMap::new()),
331 consent: ConsentService::default(),
332 }
333 }
334}
335
336impl ComplianceService<ConsentService> {
337 pub fn new(node_id: String, consent: ConsentService) -> Self {
348 Self { node_id, policies: Arc::new(DashMap::new()), consent }
349 }
350
351 async fn eval_compliance(
389 &self,
390 sources: HashSet<Resource>,
391 destination: LocalizedResource,
392 destination_policy: Option<Policy>,
393 ) -> Result<ComplianceResponse, TraceabilityError> {
394 let mut result: Result<ComplianceResponse, TraceabilityError> =
395 Ok(ComplianceResponse::Grant);
396
397 let destination_policy = if let Some(destination) = self.as_local_resource(&destination) {
399 self.get_policy(&destination)
400 } else {
401 destination_policy.ok_or(TraceabilityError::DestinationPolicyNotFound)?
402 };
403
404 let source_policies = self.get_policies(sources);
405
406 let mut consent_tasks = JoinSet::new();
408
409 for (source, source_policy) in source_policies {
410 if source_policy.get_consent() {
412 let mut consent_service = self.consent.clone();
413 let destination = destination.clone().into();
414 let source_clone = source.clone();
415 consent_tasks.spawn(async move {
416 consent_service
417 .call(ConsentRequest::RequestConsent { source: source_clone, destination })
418 .await
419 });
420 }
421
422 if source_policy.is_deleted() || destination_policy.is_deleted() {
424 info!(
425 source_deleted = source_policy.is_deleted(),
426 destination_deleted = destination_policy.is_deleted(),
427 "[compliance] EvalPolicies: deleted policies detected"
428 );
429 if source_policy.is_pending_deletion() {
430 info!("[compliance] Enforcing deletion policy for source");
431 }
432 if destination_policy.is_pending_deletion() {
433 info!("[compliance] Enforcing deletion policy for destination");
434 }
435
436 #[cfg(feature = "deletion_enforcement")]
437 if let Resource::Fd(crate::traceability::infrastructure::naming::Fd::File(file)) =
438 &source
439 {
440 let _ = std::fs::remove_file(&file.path);
441 }
442 #[cfg(feature = "deletion_enforcement")]
443 if let Resource::Fd(crate::traceability::infrastructure::naming::Fd::File(file)) =
444 &destination.resource()
445 {
446 let _ = std::fs::remove_file(&file.path);
447 }
448
449 result = Err(TraceabilityError::DirectPolicyViolation);
450 }
451
452 if source_policy.integrity < destination_policy.integrity {
455 result = Err(TraceabilityError::DirectPolicyViolation);
456 }
457
458 if source_policy.confidentiality == ConfidentialityPolicy::Secret
460 && destination_policy.confidentiality == ConfidentialityPolicy::Public
461 {
462 result = Err(TraceabilityError::DirectPolicyViolation);
463 }
464 }
465
466 while let Some(result) = consent_tasks.join_next().await {
468 let consent_response = result
469 .map_err(|_| TraceabilityError::InternalTrace2eError)?
470 .map_err(|_| TraceabilityError::InternalTrace2eError)?;
471
472 match consent_response {
473 ConsentResponse::Consent(true) => continue,
474 ConsentResponse::Consent(false) => {
475 return Err(TraceabilityError::DirectPolicyViolation);
476 }
477 _ => return Err(TraceabilityError::InternalTrace2eError),
478 }
479 }
480 result
481 }
482}
483
484impl ComplianceService {
485 fn as_local_resource(&self, resource: &LocalizedResource) -> Option<Resource> {
487 if *resource.node_id() == self.node_id {
488 Some(resource.resource().to_owned())
489 } else {
490 None
491 }
492 }
493
494 fn get_policy(&self, resource: &Resource) -> Policy {
500 self.policies.entry(resource.to_owned()).or_default().to_owned()
501 }
502
503 fn get_policies(&self, resources: HashSet<Resource>) -> HashMap<Resource, Policy> {
513 let mut policies_set = HashMap::new();
514 for resource in resources {
515 if !resource.is_stream() {
517 policies_set.insert(resource.to_owned(), self.get_policy(&resource));
518 }
519 }
520 policies_set
521 }
522
523 fn get_localized_policies(
533 &self,
534 resources: HashSet<Resource>,
535 ) -> HashMap<LocalizedResource, Policy> {
536 self.get_policies(resources)
537 .into_iter()
538 .map(|(resource, policy)| {
539 (LocalizedResource::new(self.node_id.clone(), resource), policy)
540 })
541 .collect()
542 }
543
544 fn set_policy(&self, resource: Resource, policy: Policy) -> ComplianceResponse {
559 if self.policies.get(&resource).is_some_and(|policy| policy.is_deleted()) {
561 ComplianceResponse::PolicyNotUpdated
562 } else {
563 self.policies.insert(resource, policy);
564 ComplianceResponse::PolicyUpdated
565 }
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 enforce_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::EvalCompliance { sources, destination, destination_policy } => {
682 info!(
683 node_id = %this.node_id,
684 sources = %DisplayableResource::from(&sources),
685 destination = %destination,
686 destination_policy = ?destination_policy,
687 "[compliance] EvalCompliance"
688 );
689 this.eval_compliance(sources, destination, destination_policy).await
690 }
691 ComplianceRequest::GetPolicy(resource) => {
692 info!(node_id = %this.node_id, resource = %resource, "[compliance] GetPolicy");
693 Ok(ComplianceResponse::Policy(this.get_policy(&resource)))
694 }
695 ComplianceRequest::GetPolicies(resources) => {
696 info!(
697 node_id = %this.node_id,
698 resources = %DisplayableResource::from(&resources),
699 "[compliance] GetPolicies"
700 );
701 Ok(ComplianceResponse::Policies(this.get_localized_policies(resources)))
702 }
703 ComplianceRequest::SetPolicy { resource, policy } => {
704 info!(
705 node_id = %this.node_id,
706 resource = %resource,
707 policy = ?policy,
708 "[compliance] SetPolicy"
709 );
710 Ok(this.set_policy(resource, policy))
711 }
712 ComplianceRequest::SetConfidentiality { resource, confidentiality } => {
713 info!(
714 node_id = %this.node_id,
715 resource = %resource,
716 confidentiality = ?confidentiality,
717 "[compliance] SetConfidentiality"
718 );
719 Ok(this.set_confidentiality(resource, confidentiality))
720 }
721 ComplianceRequest::SetIntegrity { resource, integrity } => {
722 info!(
723 node_id = %this.node_id,
724 resource = %resource,
725 integrity = ?integrity,
726 "[compliance] SetIntegrity"
727 );
728 Ok(this.set_integrity(resource, integrity))
729 }
730 ComplianceRequest::SetDeleted(resource) => {
731 info!(node_id = %this.node_id, resource = %resource, "[compliance] SetDeleted");
732 Ok(this.set_deleted(resource))
733 }
734 ComplianceRequest::EnforceConsent { resource, consent } => {
735 info!(
736 node_id = %this.node_id,
737 resource = %resource,
738 consent = ?consent,
739 "[compliance] EnforceConsent"
740 );
741 Ok(this.enforce_consent(resource, consent))
742 }
743 }
744 })
745 }
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use crate::traceability::infrastructure::naming::Resource;
752
753 fn create_public_policy(integrity: u32) -> Policy {
755 Policy::new(ConfidentialityPolicy::Public, integrity, DeletionPolicy::NotDeleted, false)
756 }
757
758 fn create_secret_policy(integrity: u32) -> Policy {
759 Policy::new(ConfidentialityPolicy::Secret, integrity, DeletionPolicy::NotDeleted, false)
760 }
761
762 fn create_deleted_policy() -> Policy {
763 Policy::new(ConfidentialityPolicy::Public, 0, DeletionPolicy::Pending, false)
764 }
765
766 fn init_tracing() {
767 crate::trace2e_tracing::init();
768 }
769
770 #[tokio::test]
771 async fn unit_compliance_set_policy_basic() {
772 init_tracing();
773 let compliance = ComplianceService::default();
774 let process = Resource::new_process_mock(0);
775
776 let policy = create_secret_policy(5);
777
778 assert_eq!(
780 compliance.set_policy(process.clone(), policy),
781 ComplianceResponse::PolicyUpdated
782 );
783
784 let new_policy = create_secret_policy(3);
785
786 assert_eq!(compliance.set_policy(process, new_policy), ComplianceResponse::PolicyUpdated);
788 }
789
790 #[tokio::test]
791 async fn unit_compliance_policy_evaluation_scenarios() {
792 init_tracing();
793 let compliance = ComplianceService::default();
794 let test_cases = [
796 (create_public_policy(5), create_public_policy(3), true, "integrity pass: 5 >= 3"),
798 (create_public_policy(3), create_public_policy(5), false, "integrity fail: 3 < 5"),
799 (
800 create_secret_policy(5),
801 create_secret_policy(3),
802 true,
803 "confidentiality pass: secret -> secret",
804 ),
805 (
806 create_secret_policy(5),
807 create_public_policy(3),
808 false,
809 "confidentiality fail: secret -> public",
810 ),
811 (
812 create_public_policy(5),
813 create_secret_policy(3),
814 true,
815 "confidentiality pass: public -> secret",
816 ),
817 ];
818 let mock_file =
819 LocalizedResource::new(String::new(), Resource::new_file("/tmp/dest".to_string()));
820 let mock_process = Resource::new_process_mock(0);
821
822 for (source_policy, dest_policy, should_pass, description) in test_cases {
823 compliance.set_policy(mock_process.clone(), source_policy);
824 compliance.set_policy(mock_file.resource().to_owned(), dest_policy);
825 let result = compliance
826 .eval_compliance(HashSet::from([mock_process.clone()]), mock_file.clone(), None)
827 .await;
828
829 if should_pass {
830 assert!(
831 result.is_ok_and(|r| r == ComplianceResponse::Grant),
832 "Test failed: {description}"
833 );
834 } else {
835 assert!(
836 result.is_err_and(|e| e == TraceabilityError::DirectPolicyViolation),
837 "Test failed: {description}"
838 );
839 }
840 }
841 }
842
843 #[tokio::test]
844 async fn unit_compliance_default_policies() {
845 init_tracing();
846 let compliance = ComplianceService::default();
847 let mock_local_file =
848 LocalizedResource::new(String::new(), Resource::new_file("/tmp/dest".to_string()));
849 let mock_process = Resource::new_process_mock(0);
850 let mock_process2 = Resource::new_process_mock(1);
851
852 assert!(
854 compliance
855 .eval_compliance(
856 HashSet::from([mock_process.clone(), mock_process2.clone()]),
857 mock_local_file.clone(),
858 None,
859 )
860 .await
861 .is_ok_and(|r| r == ComplianceResponse::Grant)
862 );
863
864 let dest_policy = create_public_policy(2);
866 compliance.set_policy(mock_local_file.resource().to_owned(), dest_policy);
867 assert!(
868 compliance
869 .eval_compliance(
870 HashSet::from([mock_process.clone(), mock_process2.clone()]),
871 mock_local_file, None,
873 )
874 .await
875 .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
876 );
877 }
878
879 #[tokio::test]
880 async fn unit_compliance_service_eval_policies_requests() {
881 init_tracing();
882 let mut compliance = ComplianceService::default();
883
884 let mock_process = Resource::new_process_mock(0);
885 let file =
886 LocalizedResource::new(String::new(), Resource::new_file("/tmp/dest".to_string()));
887
888 let process_policy = create_public_policy(5);
890 compliance.set_policy(mock_process.clone(), process_policy);
891 let file_policy = create_public_policy(3);
892 compliance.set_policy(file.resource().to_owned(), file_policy);
893
894 let grant_request = ComplianceRequest::EvalCompliance {
895 sources: HashSet::from([mock_process.clone()]),
896 destination: file.clone(),
897 destination_policy: None,
898 };
899 assert_eq!(compliance.call(grant_request).await.unwrap(), ComplianceResponse::Grant);
900
901 let low_process_policy = create_public_policy(2);
903 compliance.set_policy(mock_process.clone(), low_process_policy);
904
905 let deny_request = ComplianceRequest::EvalCompliance {
906 sources: HashSet::from([mock_process.clone()]),
907 destination: file,
908 destination_policy: None,
909 };
910 assert_eq!(
911 compliance.call(deny_request).await.unwrap_err(),
912 TraceabilityError::DirectPolicyViolation
913 );
914 }
915
916 #[test]
917 fn unit_compliance_get_policies_empty() {
918 init_tracing();
919 let compliance = ComplianceService::default();
920 let process = LocalizedResource::new(String::new(), Resource::new_process_mock(0));
921 let file =
922 LocalizedResource::new(String::new(), Resource::new_file("/tmp/test".to_string()));
923
924 assert_eq!(
925 compliance.get_localized_policies(HashSet::from([
926 process.resource().to_owned(),
927 file.resource().to_owned()
928 ])),
929 HashMap::from([(process, Policy::default()), (file, Policy::default())])
930 );
931 }
932
933 #[test]
934 fn unit_compliance_get_policies_scenarios() {
935 init_tracing();
936 let compliance = ComplianceService::default();
937
938 let process = Resource::new_process_mock(0);
940 let file = Resource::new_file("/tmp/test".to_string());
941
942 let process_policy = create_secret_policy(7);
944 let file_policy = create_public_policy(3);
945
946 compliance.set_policy(process.clone(), process_policy.clone());
948 assert_eq!(
949 compliance.get_policies(HashSet::from([process.clone()])),
950 HashMap::from([(process.clone(), process_policy.clone())])
951 );
952
953 compliance.set_policy(file.clone(), file_policy.clone());
955 assert_eq!(
956 compliance.get_policies(HashSet::from([process.clone(), file.clone()])),
957 HashMap::from([
958 (process.clone(), process_policy.clone()),
959 (file.clone(), file_policy.clone())
960 ])
961 );
962
963 let new_file = Resource::new_file("/tmp/new.txt".to_string());
965 assert_eq!(
966 compliance.get_policies(HashSet::from([
967 process.clone(),
968 file.clone(),
969 new_file.clone()
970 ])),
971 HashMap::from([
972 (process.clone(), process_policy.clone()),
973 (file.clone(), file_policy.clone()),
974 (new_file.clone(), Policy::default())
975 ])
976 );
977 }
978
979 #[tokio::test]
980 async fn unit_compliance_deleted_policy_behavior() {
981 init_tracing();
982 let compliance = ComplianceService::default();
983 let mock_process0 = Resource::new_process_mock(0);
984 let mock_process1 = Resource::new_process_mock(1);
985 let mock_file = Resource::new_file("/tmp/dest".to_string());
986
987 let deleted_policy = create_deleted_policy();
989 compliance.set_policy(mock_process0.clone(), deleted_policy.clone());
990
991 assert_eq!(
993 compliance.get_policies(HashSet::from([mock_process0.clone()])),
994 HashMap::from([(mock_process0.clone(), deleted_policy.clone())])
995 );
996
997 assert_eq!(
999 compliance.set_policy(mock_process0.clone(), Policy::default()),
1000 ComplianceResponse::PolicyNotUpdated
1001 );
1002
1003 assert_eq!(
1005 compliance.get_policies(HashSet::from([mock_process0.clone()])),
1006 HashMap::from([(mock_process0.clone(), deleted_policy.clone())])
1007 );
1008
1009 assert!(
1011 compliance
1012 .eval_compliance(
1013 HashSet::from([mock_process0.clone(), mock_process1.clone()]),
1014 LocalizedResource::new(String::new(), mock_file.clone()),
1015 None
1016 )
1017 .await
1018 .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
1019 );
1020
1021 assert!(
1022 compliance
1023 .eval_compliance(
1024 HashSet::from([mock_file.clone(), mock_process1.clone()]),
1025 LocalizedResource::new(String::new(), mock_process0),
1026 None
1027 )
1028 .await
1029 .is_err_and(|e| e == TraceabilityError::DirectPolicyViolation)
1030 );
1031 }
1032
1033 #[tokio::test]
1034 async fn unit_compliance_localized_destination() {
1035 init_tracing();
1036 let compliance = ComplianceService::default();
1037
1038 let local_process = Resource::new_process_mock(0);
1039 let local_file = Resource::new_file("/tmp/local.txt".to_string());
1040 let remote_process =
1041 LocalizedResource::new("10.0.0.1".to_string(), Resource::new_process_mock(1));
1042
1043 assert!(
1045 compliance
1046 .eval_compliance(
1047 HashSet::from([local_file.clone(), local_process.clone()]),
1048 remote_process.clone(),
1049 None
1050 )
1051 .await
1052 .is_err_and(|e| e == TraceabilityError::DestinationPolicyNotFound)
1053 );
1054
1055 let remote_policy = create_public_policy(0);
1057 assert!(
1058 compliance
1059 .eval_compliance(
1060 HashSet::from([local_file, local_process]),
1061 remote_process.clone(),
1062 Some(remote_policy.clone())
1063 )
1064 .await
1065 .is_ok_and(|r| r == ComplianceResponse::Grant)
1066 );
1067 }
1068
1069 #[tokio::test]
1070 async fn unit_compliance_service_deletion_policy_workflow() {
1071 init_tracing();
1072 let mut compliance = ComplianceService::default();
1073
1074 let process = Resource::new_process_mock(0);
1075 let file = Resource::new_file("/tmp/sensitive.txt".to_string());
1076
1077 let process_policy = create_secret_policy(5);
1079 let file_policy = create_public_policy(3);
1080
1081 let set_process_request = ComplianceRequest::SetPolicy {
1082 resource: process.clone(),
1083 policy: process_policy.clone(),
1084 };
1085 assert_eq!(
1086 compliance.call(set_process_request).await.unwrap(),
1087 ComplianceResponse::PolicyUpdated
1088 );
1089
1090 let set_file_request =
1091 ComplianceRequest::SetPolicy { resource: file.clone(), policy: file_policy.clone() };
1092 assert_eq!(
1093 compliance.call(set_file_request).await.unwrap(),
1094 ComplianceResponse::PolicyUpdated
1095 );
1096
1097 let get_process_request = ComplianceRequest::GetPolicy(process.clone());
1099 if let ComplianceResponse::Policy(retrieved_policy) =
1100 compliance.call(get_process_request).await.unwrap()
1101 {
1102 assert_eq!(retrieved_policy, process_policy);
1103 assert!(!retrieved_policy.is_deleted());
1104 } else {
1105 panic!("Expected Policy response");
1106 }
1107
1108 let mut pending_deletion_policy = create_secret_policy(7);
1110 pending_deletion_policy.deleted();
1111 assert!(pending_deletion_policy.is_pending_deletion());
1112
1113 let set_pending_request = ComplianceRequest::SetPolicy {
1114 resource: file.clone(),
1115 policy: pending_deletion_policy.clone(),
1116 };
1117 assert_eq!(
1118 compliance.call(set_pending_request).await.unwrap(),
1119 ComplianceResponse::PolicyUpdated
1120 );
1121
1122 let get_file_request = ComplianceRequest::GetPolicy(file.clone());
1124 if let ComplianceResponse::Policy(retrieved_policy) =
1125 compliance.call(get_file_request).await.unwrap()
1126 {
1127 assert_eq!(retrieved_policy, pending_deletion_policy);
1128 assert!(retrieved_policy.is_deleted());
1129 assert!(retrieved_policy.is_pending_deletion());
1130 } else {
1131 panic!("Expected Policy response");
1132 }
1133
1134 let new_policy = create_public_policy(1);
1136 let update_pending_request =
1137 ComplianceRequest::SetPolicy { resource: file.clone(), policy: new_policy };
1138 assert_eq!(
1139 compliance.call(update_pending_request).await.unwrap(),
1140 ComplianceResponse::PolicyNotUpdated
1141 );
1142
1143 let mut deleted_policy = create_public_policy(2);
1145 deleted_policy.deleted();
1146 deleted_policy.deletion_enforced();
1147 assert!(deleted_policy.is_deleted());
1148 assert!(!deleted_policy.is_pending_deletion());
1149
1150 let new_resource = Resource::new_process_mock(1);
1151 let set_deleted_request = ComplianceRequest::SetPolicy {
1152 resource: new_resource.clone(),
1153 policy: deleted_policy.clone(),
1154 };
1155 assert_eq!(
1156 compliance.call(set_deleted_request).await.unwrap(),
1157 ComplianceResponse::PolicyUpdated
1158 );
1159
1160 let another_policy = create_secret_policy(10);
1162 let update_deleted_request =
1163 ComplianceRequest::SetPolicy { resource: new_resource.clone(), policy: another_policy };
1164 assert_eq!(
1165 compliance.call(update_deleted_request).await.unwrap(),
1166 ComplianceResponse::PolicyNotUpdated
1167 );
1168
1169 let eval_with_deleted_source = ComplianceRequest::EvalCompliance {
1171 sources: HashSet::from([new_resource]),
1172 destination: LocalizedResource::new(
1173 String::new(),
1174 Resource::new_file("/tmp/dest".to_string()),
1175 ),
1176 destination_policy: None,
1177 };
1178 assert_eq!(
1179 compliance.call(eval_with_deleted_source).await.unwrap_err(),
1180 TraceabilityError::DirectPolicyViolation
1181 );
1182
1183 let eval_with_deleted_dest = ComplianceRequest::EvalCompliance {
1185 sources: HashSet::from([process]),
1186 destination: LocalizedResource::new(String::new(), file),
1187 destination_policy: None,
1188 };
1189 assert_eq!(
1190 compliance.call(eval_with_deleted_dest).await.unwrap_err(),
1191 TraceabilityError::DirectPolicyViolation
1192 );
1193 }
1194
1195 #[tokio::test]
1196 async fn unit_compliance_eval_policies_empty_sources() {
1197 init_tracing();
1198 let compliance = ComplianceService::default();
1199
1200 let mock_file = LocalizedResource::new(
1201 compliance.node_id.clone(),
1202 Resource::new_file("/tmp/dest".to_string()),
1203 );
1204
1205 assert!(
1208 compliance
1209 .eval_compliance(HashSet::new(), mock_file, None)
1210 .await
1211 .is_ok_and(|r| r == ComplianceResponse::Grant)
1212 );
1213 }
1214}