1pub mod ai;
2pub mod network;
3pub mod package;
4pub mod shell;
5pub mod system;
6pub mod utilities;
7
8use std::time::Duration;
9
10use serde::{Deserialize, Serialize};
11use tera::Context;
12use tokio::time::Instant;
13use tokio::time::{sleep, timeout as tokio_timeout};
14use tracing::{debug, error, info};
15
16use crate::error::RegentError;
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(untagged)]
24pub enum AttributeTimeout {
25 Default,
27 Duration(Duration),
29 Seconds(u64),
31 Milliseconds(u64),
33}
34
35impl Default for AttributeTimeout {
36 fn default() -> Self {
37 AttributeTimeout::Default
38 }
39}
40use crate::hosts::managed_host::InternalApiCallOutcome;
41use crate::hosts::privilege::Privilege;
42use crate::hosts::properties::HostProperties;
43use crate::secrets::SecretProvidersPool;
44use crate::state::Check;
45use crate::state::attribute::ai::ollama::OllamaApiCall;
46use crate::state::attribute::ai::ollama::OllamaBlockExpectedState;
47use crate::state::attribute::network::iptables::IptablesApiCall;
48use crate::state::attribute::network::iptables::IptablesBlockExpectedState;
49use crate::state::attribute::package::apt::AptApiCall;
50use crate::state::attribute::package::apt::AptBlockExpectedState;
51use crate::state::attribute::package::apt_repo::AptRepoApiCall;
52use crate::state::attribute::package::apt_repo::AptRepoBlockExpectedState;
53use crate::state::attribute::package::dnf_repo::DnfRepoApiCall;
54use crate::state::attribute::package::dnf_repo::DnfRepoBlockExpectedState;
55use crate::state::attribute::package::yumdnf::YumDnfApiCall;
56use crate::state::attribute::package::yumdnf::YumDnfBlockExpectedState;
57use crate::state::attribute::shell::command::CommandApiCall;
58use crate::state::attribute::shell::command::CommandBlockExpectedState;
59use crate::state::attribute::system::cron::CronApiCall;
60use crate::state::attribute::system::cron::CronBlockExpectedState;
61use crate::state::attribute::system::group::GroupApiCall;
62use crate::state::attribute::system::group::GroupBlockExpectedState;
63use crate::state::attribute::system::hostname::HostnameApiCall;
64use crate::state::attribute::system::hostname::HostnameBlockExpectedState;
65use crate::state::attribute::system::service::ServiceApiCall;
66use crate::state::attribute::system::service::ServiceBlockExpectedState;
67use crate::state::attribute::system::user::UserApiCall;
68use crate::state::attribute::system::user::UserBlockExpectedState;
69use crate::state::attribute::utilities::debug::DebugApiCall;
70use crate::state::attribute::utilities::debug::DebugBlockExpectedState;
71use crate::state::attribute::utilities::lineinfile::LineInFileApiCall;
72use crate::state::attribute::utilities::lineinfile::LineInFileBlockExpectedState;
73use crate::state::attribute::utilities::ping::PingApiCall;
74use crate::state::attribute::utilities::ping::PingBlockExpectedState;
75use crate::state::compliance::AttributeComplianceAssessment;
76use crate::state::compliance::AttributeComplianceResult;
77use crate::state::compliance::AttributeComplianceStatus;
78use crate::{
79 hosts::handlers::HostHandler,
80 hosts::managed_host::{AssessCompliance, ReachCompliance, Timeout},
81 state::attribute::package::pacman::{PacmanApiCall, PacmanBlockExpectedState},
82};
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(rename_all = "PascalCase")]
86pub struct Attribute {
87 pub name: Option<String>,
88 pub privilege: Privilege,
89 detail: AttributeDetail,
90 timeout: AttributeTimeout,
91}
92
93impl Attribute {
94 pub fn from(detail: AttributeDetail, privilege: Privilege, name: Option<String>) -> Attribute {
95 Attribute {
96 privilege,
97 detail,
98 name,
99 timeout: AttributeTimeout::Default,
100 }
101 }
102
103 pub fn name(&self) -> String {
104 match self.name {
105 Some(ref name) => name.clone(),
106 None => match self.detail {
107 AttributeDetail::Apt(_) => "Apt".to_string(),
108 AttributeDetail::AptRepo(_) => "AptRepo".to_string(),
109 AttributeDetail::YumDnf(_) => "YumDnf".to_string(),
110 AttributeDetail::DnfRepo(_) => "DnfRepo".to_string(),
111 AttributeDetail::Pacman(_) => "Pacman".to_string(),
112 AttributeDetail::Service(_) => "Service".to_string(),
113 AttributeDetail::Command(_) => "Command".to_string(),
114 AttributeDetail::LineInFile(_) => "LineInFile".to_string(),
115 AttributeDetail::Ping(_) => "Ping".to_string(),
116 AttributeDetail::Debug(_) => "Debug".to_string(),
117 AttributeDetail::User(_) => "User".to_string(),
118 AttributeDetail::Group(_) => "Group".to_string(),
119 AttributeDetail::Cron(_) => "Cron".to_string(),
120 AttributeDetail::Hostname(_) => "Hostname".to_string(),
121 AttributeDetail::Iptables(_) => "Iptables".to_string(),
122 AttributeDetail::Ollama(_) => "Ollama".to_string(),
123 },
124 }
125 }
126
127 pub fn consider_context(&self, context: &Context) -> Result<Attribute, RegentError> {
128 let serialized_self = match serde_json::to_string(self) {
133 Ok(serialized_self) => serialized_self,
134 Err(details) => {
135 return Err(RegentError::InternalLogicError(format!(
137 "Attribute tried to serialize self but failed : {}",
138 details
139 )));
140 }
141 };
142
143 let context_wise_serialized_self =
144 match tera::Tera::one_off(serialized_self.as_str(), context, true) {
145 Ok(context_aware_attribute) => context_aware_attribute,
146 Err(details) => {
147 return Err(RegentError::FailureToConsiderContext(format!(
148 "Failed to consider dynamic context : {}",
149 details
150 )));
151 }
152 };
153 match serde_json::from_str::<Attribute>(&context_wise_serialized_self) {
154 Ok(context_aware_attribute) => {
155 context_aware_attribute.check().map_err(|e| {
158 RegentError::FailureToConsiderContext(format!(
159 "Post-template validation failed: {}",
160 e
161 ))
162 })?;
163 Ok(context_aware_attribute)
164 }
165 Err(detail) => Err(RegentError::FailureToConsiderContext(format!("{}", detail))),
166 }
167 }
168
169 pub async fn assess<Handler: HostHandler>(
171 &self,
172 host_handler: &mut Handler,
173 host_properties: &Option<HostProperties>,
174 optional_secret_provider: &Option<SecretProvidersPool>,
175 ) -> Result<AttributeComplianceAssessment, RegentError> {
176 self.detail
177 .assess(
178 host_handler,
179 host_properties,
180 &self.privilege,
181 optional_secret_provider,
182 self.timeout()?,
183 )
184 .await
185 }
186
187 pub async fn reach_compliance<Handler: HostHandler>(
188 &self,
189 host_handler: &mut Handler,
190 host_properties: &Option<HostProperties>,
191 optional_secret_provider: &Option<SecretProvidersPool>,
192 ) -> Result<AttributeComplianceResult, RegentError> {
193 self.detail
194 .reach_compliance(
195 host_handler,
196 host_properties,
197 &self.privilege,
198 optional_secret_provider,
199 self.timeout()?,
200 )
201 .await
202 }
203
204 pub fn check(&self) -> Result<(), RegentError> {
205 self.detail.check()
206 }
207
208 pub fn timeout(&self) -> Result<Duration, RegentError> {
209 match &self.timeout {
210 AttributeTimeout::Default => Ok(self.detail.default_timeout()),
211 AttributeTimeout::Duration(duration) => Ok(*duration),
212 AttributeTimeout::Seconds(seconds) => Ok(Duration::from_secs(*seconds)),
213 AttributeTimeout::Milliseconds(milliseconds) => {
214 Ok(Duration::from_millis(*milliseconds))
215 }
216 }
217 }
218
219 pub fn with_timeout(mut self, user_defined_timeout: Duration) -> Self {
223 self.timeout = AttributeTimeout::Duration(user_defined_timeout);
224 self
225 }
226
227 pub fn with_timeout_secs(mut self, seconds: u64) -> Self {
229 self.timeout = AttributeTimeout::Seconds(seconds);
230 self
231 }
232
233 pub fn with_timeout_millis(mut self, milliseconds: u64) -> Self {
235 self.timeout = AttributeTimeout::Milliseconds(milliseconds);
236 self
237 }
238
239 pub fn apt(
240 details: AptBlockExpectedState,
241 privilege: Privilege,
242 name: Option<String>,
243 ) -> Attribute {
244 Attribute::from(AttributeDetail::Apt(details), privilege, name)
245 }
246
247 pub fn pacman(
248 details: PacmanBlockExpectedState,
249 privilege: Privilege,
250 name: Option<String>,
251 ) -> Attribute {
252 Attribute::from(AttributeDetail::Pacman(details), privilege, name)
253 }
254
255 pub fn yumdnf(
256 details: YumDnfBlockExpectedState,
257 privilege: Privilege,
258 name: Option<String>,
259 ) -> Attribute {
260 Attribute::from(AttributeDetail::YumDnf(details), privilege, name)
261 }
262
263 pub fn command(
264 details: CommandBlockExpectedState,
265 privilege: Privilege,
266 name: Option<String>,
267 ) -> Attribute {
268 Attribute::from(AttributeDetail::Command(details), privilege, name)
269 }
270
271 pub fn service(
272 details: ServiceBlockExpectedState,
273 privilege: Privilege,
274 name: Option<String>,
275 ) -> Attribute {
276 Attribute::from(AttributeDetail::Service(details), privilege, name)
277 }
278
279 pub fn debug(
280 details: DebugBlockExpectedState,
281 privilege: Privilege,
282 name: Option<String>,
283 ) -> Attribute {
284 Attribute::from(AttributeDetail::Debug(details), privilege, name)
285 }
286
287 pub fn lineinfile(
288 details: LineInFileBlockExpectedState,
289 privilege: Privilege,
290 name: Option<String>,
291 ) -> Attribute {
292 Attribute::from(AttributeDetail::LineInFile(details), privilege, name)
293 }
294
295 pub fn ping(
296 details: PingBlockExpectedState,
297 privilege: Privilege,
298 name: Option<String>,
299 ) -> Attribute {
300 Attribute::from(AttributeDetail::Ping(details), privilege, name)
301 }
302
303 pub fn user(
304 details: UserBlockExpectedState,
305 privilege: Privilege,
306 name: Option<String>,
307 ) -> Attribute {
308 Attribute::from(AttributeDetail::User(details), privilege, name)
309 }
310
311 pub fn group(
312 details: GroupBlockExpectedState,
313 privilege: Privilege,
314 name: Option<String>,
315 ) -> Attribute {
316 Attribute::from(AttributeDetail::Group(details), privilege, name)
317 }
318
319 pub fn cron(
320 details: CronBlockExpectedState,
321 privilege: Privilege,
322 name: Option<String>,
323 ) -> Attribute {
324 Attribute::from(AttributeDetail::Cron(details), privilege, name)
325 }
326
327 pub fn hostname(
328 details: HostnameBlockExpectedState,
329 privilege: Privilege,
330 name: Option<String>,
331 ) -> Attribute {
332 Attribute::from(AttributeDetail::Hostname(details), privilege, name)
333 }
334
335 pub fn apt_repo(
336 details: AptRepoBlockExpectedState,
337 privilege: Privilege,
338 name: Option<String>,
339 ) -> Attribute {
340 Attribute::from(AttributeDetail::AptRepo(details), privilege, name)
341 }
342
343 pub fn dnf_repo(
344 details: DnfRepoBlockExpectedState,
345 privilege: Privilege,
346 name: Option<String>,
347 ) -> Attribute {
348 Attribute::from(AttributeDetail::DnfRepo(details), privilege, name)
349 }
350
351 pub fn iptables(
352 details: IptablesBlockExpectedState,
353 privilege: Privilege,
354 name: Option<String>,
355 ) -> Attribute {
356 Attribute::from(AttributeDetail::Iptables(details), privilege, name)
357 }
358
359 pub fn ollama(
360 details: OllamaBlockExpectedState,
361 privilege: Privilege,
362 name: Option<String>,
363 ) -> Attribute {
364 Attribute::from(AttributeDetail::Ollama(details), privilege, name) }
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
369#[serde(rename_all = "PascalCase")]
370pub enum AttributeDetail {
371 Apt(AptBlockExpectedState),
372 AptRepo(AptRepoBlockExpectedState),
373 YumDnf(YumDnfBlockExpectedState),
374 DnfRepo(DnfRepoBlockExpectedState),
375 Pacman(PacmanBlockExpectedState),
376 LineInFile(LineInFileBlockExpectedState),
377 Debug(DebugBlockExpectedState),
378 Ping(PingBlockExpectedState),
379 Service(ServiceBlockExpectedState),
380 Command(CommandBlockExpectedState),
381 User(UserBlockExpectedState),
382 Group(GroupBlockExpectedState),
383 Cron(CronBlockExpectedState),
384 Hostname(HostnameBlockExpectedState),
385 Iptables(IptablesBlockExpectedState),
386 Ollama(OllamaBlockExpectedState),
387}
388
389impl AttributeDetail {
390 pub fn default_timeout(&self) -> Duration {
391 match self {
392 AttributeDetail::Apt(details) => details.default_timeout(),
393 AttributeDetail::AptRepo(details) => details.default_timeout(),
394 AttributeDetail::YumDnf(details) => details.default_timeout(),
395 AttributeDetail::DnfRepo(details) => details.default_timeout(),
396 AttributeDetail::Pacman(details) => details.default_timeout(),
397 AttributeDetail::LineInFile(details) => details.default_timeout(),
398 AttributeDetail::Debug(details) => details.default_timeout(),
399 AttributeDetail::Ping(details) => details.default_timeout(),
400 AttributeDetail::Service(details) => details.default_timeout(),
401 AttributeDetail::Command(details) => details.default_timeout(),
402 AttributeDetail::User(details) => details.default_timeout(),
403 AttributeDetail::Group(details) => details.default_timeout(),
404 AttributeDetail::Cron(details) => details.default_timeout(),
405 AttributeDetail::Hostname(details) => details.default_timeout(),
406 AttributeDetail::Iptables(details) => details.default_timeout(),
407 AttributeDetail::Ollama(details) => details.default_timeout(),
408 }
409 }
410
411 pub async fn assess<Handler: HostHandler>(
412 &self,
413 host_handler: &mut Handler,
414 host_properties: &Option<HostProperties>,
415 privilege: &Privilege,
416 optional_secret_provider: &Option<SecretProvidersPool>,
417 timeout_duration: Duration,
418 ) -> Result<AttributeComplianceAssessment, RegentError> {
419 match tokio_timeout(
420 timeout_duration,
421 self.raw_assess(
422 host_handler,
423 host_properties,
424 privilege,
425 optional_secret_provider,
426 ),
427 )
428 .await
429 {
430 Ok(raw_assesment_result) => raw_assesment_result,
431 Err(_details) => {
432 error!(timeout = ?timeout_duration, "Timeout elapsed");
433 return Err(RegentError::TimeOutReached(format!(
434 "Timeout elapsed ({} ms) trying to assess compliance",
435 timeout_duration.as_millis()
436 )));
437 }
438 }
439 }
440
441 pub async fn raw_assess<Handler: HostHandler>(
442 &self,
443 host_handler: &mut Handler,
444 host_properties: &Option<HostProperties>,
445 privilege: &Privilege,
446 optional_secret_provider: &Option<SecretProvidersPool>,
447 ) -> Result<AttributeComplianceAssessment, RegentError> {
448 match self {
449 AttributeDetail::Apt(expected_state_criteria) => {
450 expected_state_criteria
451 .assess_compliance(
452 host_handler,
453 host_properties,
454 privilege,
455 optional_secret_provider,
456 )
457 .await
458 }
459 AttributeDetail::AptRepo(expected_state_criteria) => {
460 expected_state_criteria
461 .assess_compliance(
462 host_handler,
463 host_properties,
464 privilege,
465 optional_secret_provider,
466 )
467 .await
468 }
469 AttributeDetail::YumDnf(expected_state_criteria) => {
470 expected_state_criteria
471 .assess_compliance(
472 host_handler,
473 host_properties,
474 privilege,
475 optional_secret_provider,
476 )
477 .await
478 }
479 AttributeDetail::DnfRepo(expected_state_criteria) => {
480 expected_state_criteria
481 .assess_compliance(
482 host_handler,
483 host_properties,
484 privilege,
485 optional_secret_provider,
486 )
487 .await
488 }
489 AttributeDetail::Pacman(expected_state_criteria) => {
490 expected_state_criteria
491 .assess_compliance(
492 host_handler,
493 host_properties,
494 privilege,
495 optional_secret_provider,
496 )
497 .await
498 }
499 AttributeDetail::LineInFile(expected_state_criteria) => {
500 expected_state_criteria
501 .assess_compliance(
502 host_handler,
503 host_properties,
504 privilege,
505 optional_secret_provider,
506 )
507 .await
508 }
509 AttributeDetail::Debug(expected_state_criteria) => {
510 expected_state_criteria
511 .assess_compliance(
512 host_handler,
513 host_properties,
514 privilege,
515 optional_secret_provider,
516 )
517 .await
518 }
519 AttributeDetail::Ping(expected_state_criteria) => {
520 expected_state_criteria
521 .assess_compliance(
522 host_handler,
523 host_properties,
524 privilege,
525 optional_secret_provider,
526 )
527 .await
528 }
529 AttributeDetail::Service(expected_state_criteria) => {
530 expected_state_criteria
531 .assess_compliance(
532 host_handler,
533 host_properties,
534 privilege,
535 optional_secret_provider,
536 )
537 .await
538 }
539 AttributeDetail::Command(expected_state_criteria) => {
540 expected_state_criteria
541 .assess_compliance(
542 host_handler,
543 host_properties,
544 privilege,
545 optional_secret_provider,
546 )
547 .await
548 }
549 AttributeDetail::User(expected_state_criteria) => {
550 expected_state_criteria
551 .assess_compliance(
552 host_handler,
553 host_properties,
554 privilege,
555 optional_secret_provider,
556 )
557 .await
558 }
559 AttributeDetail::Group(expected_state_criteria) => {
560 expected_state_criteria
561 .assess_compliance(
562 host_handler,
563 host_properties,
564 privilege,
565 optional_secret_provider,
566 )
567 .await
568 }
569 AttributeDetail::Cron(expected_state_criteria) => {
570 expected_state_criteria
571 .assess_compliance(
572 host_handler,
573 host_properties,
574 privilege,
575 optional_secret_provider,
576 )
577 .await
578 }
579 AttributeDetail::Hostname(expected_state_criteria) => {
580 expected_state_criteria
581 .assess_compliance(
582 host_handler,
583 host_properties,
584 privilege,
585 optional_secret_provider,
586 )
587 .await
588 }
589 AttributeDetail::Iptables(expected_state_criteria) => {
590 expected_state_criteria
591 .assess_compliance(
592 host_handler,
593 host_properties,
594 privilege,
595 optional_secret_provider,
596 )
597 .await
598 }
599 AttributeDetail::Ollama(expected_state_criteria) => {
600 expected_state_criteria
601 .assess_compliance(
602 host_handler,
603 host_properties,
604 privilege,
605 optional_secret_provider,
606 )
607 .await
608 }
609 }
610 }
611
612 pub async fn reach_compliance<Handler: HostHandler>(
613 &self,
614 host_handler: &mut Handler,
615 host_properties: &Option<HostProperties>,
616 privilege: &Privilege,
617 optional_secret_provider: &Option<SecretProvidersPool>,
618 timeout_duration: Duration,
619 ) -> Result<AttributeComplianceResult, RegentError> {
620 let raw_assesment_result = match tokio_timeout(
621 timeout_duration,
622 self.raw_assess(
623 host_handler,
624 host_properties,
625 privilege,
626 optional_secret_provider,
627 ),
628 )
629 .await
630 {
631 Ok(raw_assesment_result) => raw_assesment_result,
632 Err(_details) => {
633 error!(timeout = ?timeout_duration, "Timeout elapsed");
634 return Err(RegentError::TimeOutReached(format!(
635 "Timeout elapsed ({} ms) trying to assess compliance",
636 timeout_duration.as_millis()
637 )));
638 }
639 };
640 match raw_assesment_result {
641 Ok(attribute_compliance) => {
642 info!(timeout = ?timeout_duration, "Start of reach compliance try");
643 match tokio_timeout(
644 timeout_duration,
645 self.raw_reach_compliance(
646 host_handler,
647 host_properties,
648 privilege,
649 optional_secret_provider,
650 attribute_compliance,
651 ),
652 )
653 .await
654 {
655 Ok(raw_compliance_reaching_result) => raw_compliance_reaching_result,
656 Err(_details) => {
657 error!(timeout = ?timeout_duration, "Timeout elapsed");
658 return Err(RegentError::TimeOutReached(format!(
659 "Timeout elapsed ({} ms) trying to assess compliance",
660 timeout_duration.as_millis()
661 )));
662 }
663 }
664 }
665 Err(details) => Err(details),
666 }
667 }
668
669 pub async fn raw_reach_compliance<Handler: HostHandler>(
670 &self,
671 host_handler: &mut Handler,
672 host_properties: &Option<HostProperties>,
673 privilege: &Privilege,
674 optional_secret_provider: &Option<SecretProvidersPool>,
675 attribute_compliance: AttributeComplianceAssessment,
676 ) -> Result<AttributeComplianceResult, RegentError> {
677 match attribute_compliance {
678 AttributeComplianceAssessment::Compliant => Ok(AttributeComplianceResult::from(
679 AttributeComplianceStatus::AlreadyCompliant,
680 None,
681 )),
682 AttributeComplianceAssessment::NonCompliant(remediations) => {
683 if remediations.is_empty() {
684 return Err(RegentError::InternalLogicError(format!(
685 "This should not have been called as the ManagedHost is already compliant"
686 )));
687 }
688
689 let mut actions_taken: Vec<(Remediation, InternalApiCallOutcome)> = Vec::new();
690
691 for remediation_ref in remediations.iter() {
692 let (remediation, internal_api_call_outcome) = match remediation_ref {
693 Remediation::None(message) => {
694 return Err(RegentError::InternalLogicError(format!(
695 "Remediation::None({}) : get rid of this",
696 message
697 )));
698 }
699 Remediation::Pacman(attribute_api_call) => match attribute_api_call
700 .call(host_handler, host_properties, optional_secret_provider)
701 .await
702 {
703 Ok(internal_api_call_outcome) => {
704 (remediation_ref.clone(), internal_api_call_outcome)
705 }
706 Err(details) => {
707 return Err(details);
708 }
709 },
710 Remediation::Apt(attribute_api_call) => {
711 match attribute_api_call
712 .call(host_handler, host_properties, optional_secret_provider)
713 .await
714 {
715 Ok(internal_api_call_outcome) => {
716 (remediation_ref.clone(), internal_api_call_outcome)
717 }
718 Err(details) => {
719 return Err(details);
720 }
721 }
722 }
723 Remediation::AptRepo(attribute_api_call) => {
724 match attribute_api_call
725 .call(host_handler, host_properties, optional_secret_provider)
726 .await
727 {
728 Ok(internal_api_call_outcome) => {
729 (remediation_ref.clone(), internal_api_call_outcome)
730 }
731 Err(details) => {
732 return Err(details);
733 }
734 }
735 }
736 Remediation::YumDnf(attribute_api_call) => match attribute_api_call
737 .call(host_handler, host_properties, optional_secret_provider)
738 .await
739 {
740 Ok(internal_api_call_outcome) => {
741 (remediation_ref.clone(), internal_api_call_outcome)
742 }
743 Err(details) => {
744 return Err(details);
745 }
746 },
747 Remediation::DnfRepo(attribute_api_call) => match attribute_api_call
748 .call(host_handler, host_properties, optional_secret_provider)
749 .await
750 {
751 Ok(internal_api_call_outcome) => {
752 (remediation_ref.clone(), internal_api_call_outcome)
753 }
754 Err(details) => {
755 return Err(details);
756 }
757 },
758 Remediation::LineInFile(attribute_api_call) => match attribute_api_call
759 .call(host_handler, host_properties, optional_secret_provider)
760 .await
761 {
762 Ok(internal_api_call_outcome) => {
763 (remediation_ref.clone(), internal_api_call_outcome)
764 }
765 Err(details) => {
766 return Err(details);
767 }
768 },
769 Remediation::Debug(attribute_api_call) => {
770 match attribute_api_call
771 .call(host_handler, host_properties, optional_secret_provider)
772 .await
773 {
774 Ok(internal_api_call_outcome) => {
775 (remediation_ref.clone(), internal_api_call_outcome)
776 }
777 Err(details) => {
778 return Err(details);
779 }
780 }
781 }
782 Remediation::Ping(attribute_api_call) => {
783 match attribute_api_call
784 .call(host_handler, host_properties, optional_secret_provider)
785 .await
786 {
787 Ok(internal_api_call_outcome) => {
788 (remediation_ref.clone(), internal_api_call_outcome)
789 }
790 Err(details) => {
791 return Err(details);
792 }
793 }
794 }
795 Remediation::Service(attribute_api_call) => {
796 match attribute_api_call
797 .call(host_handler, host_properties, optional_secret_provider)
798 .await
799 {
800 Ok(internal_api_call_outcome) => {
801 (remediation_ref.clone(), internal_api_call_outcome)
802 }
803 Err(details) => {
804 return Err(details);
805 }
806 }
807 }
808 Remediation::Command(attribute_api_call) => {
809 match attribute_api_call
810 .call(host_handler, host_properties, optional_secret_provider)
811 .await
812 {
813 Ok(internal_api_call_outcome) => {
814 (remediation_ref.clone(), internal_api_call_outcome)
815 }
816 Err(details) => {
817 return Err(details);
818 }
819 }
820 }
821 Remediation::User(attribute_api_call) => {
822 match attribute_api_call
823 .call(host_handler, host_properties, optional_secret_provider)
824 .await
825 {
826 Ok(internal_api_call_outcome) => {
827 (remediation_ref.clone(), internal_api_call_outcome)
828 }
829 Err(details) => {
830 return Err(details);
831 }
832 }
833 }
834 Remediation::Group(attribute_api_call) => {
835 match attribute_api_call
836 .call(host_handler, host_properties, optional_secret_provider)
837 .await
838 {
839 Ok(internal_api_call_outcome) => {
840 (remediation_ref.clone(), internal_api_call_outcome)
841 }
842 Err(details) => {
843 return Err(details);
844 }
845 }
846 }
847 Remediation::Cron(attribute_api_call) => {
848 match attribute_api_call
849 .call(host_handler, host_properties, optional_secret_provider)
850 .await
851 {
852 Ok(internal_api_call_outcome) => {
853 (remediation_ref.clone(), internal_api_call_outcome)
854 }
855 Err(details) => {
856 return Err(details);
857 }
858 }
859 }
860 Remediation::Hostname(attribute_api_call) => {
861 match attribute_api_call
862 .call(host_handler, host_properties, optional_secret_provider)
863 .await
864 {
865 Ok(internal_api_call_outcome) => {
866 (remediation_ref.clone(), internal_api_call_outcome)
867 }
868 Err(details) => {
869 return Err(details);
870 }
871 }
872 }
873 Remediation::Iptables(attribute_api_call) => {
874 match attribute_api_call
875 .call(host_handler, host_properties, optional_secret_provider)
876 .await
877 {
878 Ok(internal_api_call_outcome) => {
879 (remediation_ref.clone(), internal_api_call_outcome)
880 }
881 Err(details) => {
882 return Err(details);
883 }
884 }
885 }
886 Remediation::Ollama(attribute_api_call) => {
887 match attribute_api_call
888 .call(host_handler, host_properties, optional_secret_provider)
889 .await
890 {
891 Ok(internal_api_call_outcome) => {
892 (remediation_ref.clone(), internal_api_call_outcome)
893 }
894 Err(details) => {
895 return Err(details);
896 }
897 }
898 }
899 };
900
901 actions_taken.push((remediation, internal_api_call_outcome.clone()));
902
903 if let InternalApiCallOutcome::Failure(_detail) = &internal_api_call_outcome {
904 return Ok(AttributeComplianceResult::from(
905 AttributeComplianceStatus::FailedReachedCompliance,
906 Some(actions_taken),
907 ));
908 }
909 }
910
911 Ok(AttributeComplianceResult::from(
912 AttributeComplianceStatus::ReachedCompliance,
913 Some(actions_taken),
914 ))
915 }
916 }
917 }
918
919 pub fn check(&self) -> Result<(), RegentError> {
920 match self {
921 AttributeDetail::Apt(expected_state_block) => expected_state_block.check(),
922 AttributeDetail::AptRepo(expected_state_block) => expected_state_block.check(),
923 AttributeDetail::YumDnf(expected_state_block) => expected_state_block.check(),
924 AttributeDetail::DnfRepo(expected_state_block) => expected_state_block.check(),
925 AttributeDetail::Pacman(expected_state_block) => expected_state_block.check(),
926 AttributeDetail::LineInFile(expected_state_block) => expected_state_block.check(),
927 AttributeDetail::Debug(expected_state_block) => expected_state_block.check(),
928 AttributeDetail::Ping(expected_state_block) => expected_state_block.check(),
929 AttributeDetail::Service(expected_state_block) => expected_state_block.check(),
930 AttributeDetail::Command(expected_state_block) => expected_state_block.check(),
931 AttributeDetail::User(expected_state_block) => expected_state_block.check(),
932 AttributeDetail::Group(expected_state_block) => expected_state_block.check(),
933 AttributeDetail::Cron(expected_state_block) => expected_state_block.check(),
934 AttributeDetail::Hostname(expected_state_block) => expected_state_block.check(),
935 AttributeDetail::Iptables(expected_state_block) => expected_state_block.check(),
936 AttributeDetail::Ollama(expected_state_block) => expected_state_block.check(),
937 }
938 }
939}
940
941#[derive(Clone, Serialize, Deserialize)]
942pub enum Remediation {
943 None(String),
944 Pacman(PacmanApiCall),
945 Apt(AptApiCall),
946 AptRepo(AptRepoApiCall),
947 YumDnf(YumDnfApiCall),
948 DnfRepo(DnfRepoApiCall),
949 LineInFile(LineInFileApiCall),
950 Debug(DebugApiCall),
951 Ping(PingApiCall),
952 Service(ServiceApiCall),
953 Command(CommandApiCall),
954 User(UserApiCall),
955 Group(GroupApiCall),
956 Cron(CronApiCall),
957 Hostname(HostnameApiCall),
958 Iptables(IptablesApiCall),
959 Ollama(OllamaApiCall),
960}
961
962impl std::fmt::Debug for Remediation {
963 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
964 match self {
965 Remediation::None(details) => write!(f, "{}", details),
966 Remediation::Pacman(api_call) => write!(f, "{}", api_call.display()),
967 Remediation::Apt(api_call) => write!(f, "{}", api_call.display()),
968 Remediation::AptRepo(api_call) => write!(f, "{}", api_call.display()),
969 Remediation::YumDnf(api_call) => write!(f, "{}", api_call.display()),
970 Remediation::DnfRepo(api_call) => write!(f, "{}", api_call.display()),
971 Remediation::LineInFile(api_call) => write!(f, "{}", api_call.display()),
972 Remediation::Debug(api_call) => write!(f, "{}", api_call.display()),
973 Remediation::Ping(api_call) => write!(f, "{}", api_call.display()),
974 Remediation::Service(api_call) => write!(f, "{}", api_call.display()),
975 Remediation::Command(api_call) => write!(f, "{}", api_call.display()),
976 Remediation::User(api_call) => write!(f, "{}", api_call.display()),
977 Remediation::Group(api_call) => write!(f, "{}", api_call.display()),
978 Remediation::Cron(api_call) => write!(f, "{}", api_call.display()),
979 Remediation::Hostname(api_call) => write!(f, "{}", api_call.display()),
980 Remediation::Iptables(api_call) => write!(f, "{}", api_call.display()),
981 Remediation::Ollama(api_call) => write!(f, "{}", api_call.display()),
982 }
983 }
984}
985
986impl Remediation {
987 pub async fn reach_compliance<Handler: HostHandler>(
988 &self,
989 host_handler: &mut Handler,
990 host_properties: &Option<HostProperties>,
991 optional_secret_provider: &Option<SecretProvidersPool>,
992 timeout_duration: Duration,
993 ) -> Result<InternalApiCallOutcome, RegentError> {
994 match tokio_timeout(
995 timeout_duration,
996 self.raw_reach_compliance(host_handler, host_properties, optional_secret_provider),
997 )
998 .await
999 {
1000 Ok(raw_assesment_result) => raw_assesment_result,
1001 Err(_details) => {
1002 error!(timeout = ?timeout_duration, "Timeout elapsed");
1003 return Err(RegentError::TimeOutReached(format!(
1004 "Timeout elapsed ({} ms) trying to run internal API call",
1005 timeout_duration.as_millis()
1006 )));
1007 }
1008 }
1009 }
1010
1011 pub async fn raw_reach_compliance<Handler: HostHandler>(
1012 &self,
1013 host_handler: &mut Handler,
1014 host_properties: &Option<HostProperties>,
1015 optional_secret_provider: &Option<SecretProvidersPool>,
1016 ) -> Result<InternalApiCallOutcome, RegentError> {
1017 match self {
1018 Remediation::None(_) => {
1019 Err(RegentError::InternalLogicError(String::from(
1021 "Unexpected remediation",
1022 )))
1023 }
1024 Remediation::Pacman(api_call) => {
1025 api_call
1026 .call(host_handler, host_properties, optional_secret_provider)
1027 .await
1028 }
1029 Remediation::Apt(api_call) => {
1030 api_call
1031 .call(host_handler, host_properties, optional_secret_provider)
1032 .await
1033 }
1034 Remediation::AptRepo(api_call) => {
1035 api_call
1036 .call(host_handler, host_properties, optional_secret_provider)
1037 .await
1038 }
1039 Remediation::YumDnf(api_call) => {
1040 api_call
1041 .call(host_handler, host_properties, optional_secret_provider)
1042 .await
1043 }
1044 Remediation::DnfRepo(api_call) => {
1045 api_call
1046 .call(host_handler, host_properties, optional_secret_provider)
1047 .await
1048 }
1049 Remediation::LineInFile(api_call) => {
1050 api_call
1051 .call(host_handler, host_properties, optional_secret_provider)
1052 .await
1053 }
1054 Remediation::Debug(api_call) => {
1055 api_call
1056 .call(host_handler, host_properties, optional_secret_provider)
1057 .await
1058 }
1059 Remediation::Ping(api_call) => {
1060 api_call
1061 .call(host_handler, host_properties, optional_secret_provider)
1062 .await
1063 }
1064 Remediation::Service(api_call) => {
1065 api_call
1066 .call(host_handler, host_properties, optional_secret_provider)
1067 .await
1068 }
1069 Remediation::Command(api_call) => {
1070 api_call
1071 .call(host_handler, host_properties, optional_secret_provider)
1072 .await
1073 }
1074 Remediation::User(api_call) => {
1075 api_call
1076 .call(host_handler, host_properties, optional_secret_provider)
1077 .await
1078 }
1079 Remediation::Group(api_call) => {
1080 api_call
1081 .call(host_handler, host_properties, optional_secret_provider)
1082 .await
1083 }
1084 Remediation::Cron(api_call) => {
1085 api_call
1086 .call(host_handler, host_properties, optional_secret_provider)
1087 .await
1088 }
1089 Remediation::Hostname(api_call) => {
1090 api_call
1091 .call(host_handler, host_properties, optional_secret_provider)
1092 .await
1093 }
1094 Remediation::Iptables(api_call) => {
1095 api_call
1096 .call(host_handler, host_properties, optional_secret_provider)
1097 .await
1098 }
1099 Remediation::Ollama(api_call) => {
1100 api_call
1101 .call(host_handler, host_properties, optional_secret_provider)
1102 .await
1103 }
1104 }
1105 }
1106
1107 pub fn display(&self) -> String {
1108 match self {
1109 Remediation::None(s) => format!("None({})", s),
1110 Remediation::Pacman(api_call) => api_call.display(),
1111 Remediation::Apt(api_call) => api_call.display(),
1112 Remediation::AptRepo(api_call) => api_call.display(),
1113 Remediation::YumDnf(api_call) => api_call.display(),
1114 Remediation::DnfRepo(api_call) => api_call.display(),
1115 Remediation::LineInFile(api_call) => api_call.display(),
1116 Remediation::Debug(api_call) => api_call.display(),
1117 Remediation::Ping(api_call) => api_call.display(),
1118 Remediation::Service(api_call) => api_call.display(),
1119 Remediation::Command(api_call) => api_call.display(),
1120 Remediation::User(api_call) => api_call.display(),
1121 Remediation::Group(api_call) => api_call.display(),
1122 Remediation::Cron(api_call) => api_call.display(),
1123 Remediation::Hostname(api_call) => api_call.display(),
1124 Remediation::Iptables(api_call) => api_call.display(),
1125 Remediation::Ollama(api_call) => api_call.display(),
1126 }
1127 }
1128}
1129
1130#[derive(Debug, Clone, Serialize, Deserialize)]
1132pub struct RemediationsList {
1133 inner: Vec<Remediation>,
1134}
1135
1136impl RemediationsList {
1137 pub fn from(remediations: Vec<Remediation>) -> Result<RemediationsList, RegentError> {
1138 if remediations.len() == 0 {
1139 Err(RegentError::InternalLogicError(format!(
1140 "Empty remediation list passed"
1141 )))
1142 } else {
1143 Ok(RemediationsList {
1144 inner: remediations,
1145 })
1146 }
1147 }
1148
1149 pub fn remediations(&self) -> &Vec<Remediation> {
1150 &self.inner
1151 }
1152
1153 pub fn into_inner(self) -> Vec<Remediation> {
1154 self.inner
1155 }
1156
1157 pub fn len(&self) -> usize {
1158 self.inner.len()
1159 }
1160
1161 pub fn is_empty(&self) -> bool {
1162 self.inner.is_empty()
1163 }
1164
1165 pub fn iter(&self) -> std::slice::Iter<'_, Remediation> {
1166 self.inner.iter()
1167 }
1168}
1169
1170impl IntoIterator for RemediationsList {
1171 type Item = Remediation;
1172 type IntoIter = std::vec::IntoIter<Remediation>;
1173
1174 fn into_iter(self) -> Self::IntoIter {
1175 self.inner.into_iter()
1176 }
1177}
1178
1179impl<'a> IntoIterator for &'a RemediationsList {
1180 type Item = &'a Remediation;
1181 type IntoIter = std::slice::Iter<'a, Remediation>;
1182
1183 fn into_iter(self) -> Self::IntoIter {
1184 self.inner.iter()
1185 }
1186}
1187
1188impl FromIterator<Remediation> for RemediationsList {
1189 fn from_iter<T: IntoIterator<Item = Remediation>>(iter: T) -> Self {
1190 RemediationsList {
1191 inner: Vec::from_iter(iter),
1192 }
1193 }
1194}