regent_sdk/state/attribute/shell/
command.rs1use crate::error::RegentError;
37use crate::hosts::managed_host::InternalApiCallOutcome;
38use crate::hosts::managed_host::{AssessCompliance, ReachCompliance, Timeout};
39use crate::hosts::properties::{HostProperties, InitSystem, LinuxFlavor, LinuxSpecifics, OsKind};
40use crate::secrets::{SecretProvidersPool, SecretReference};
41use crate::state::Check;
42use crate::state::attribute::HostHandler;
43use crate::state::attribute::Privilege;
44use crate::state::attribute::Remediation;
45use crate::state::attribute::RemediationsList;
46use crate::state::compliance::AttributeComplianceAssessment;
47use crate::state::expected_state::Parameter;
48use serde::{Deserialize, Serialize};
49use std::time::Duration;
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
56#[serde(deny_unknown_fields)]
57#[serde(rename_all = "PascalCase")]
58pub struct CommandExpectedState {
59 cmd: Parameter<String>,
61}
62
63impl Timeout for CommandExpectedState {
64 fn default_timeout(&self) -> Duration {
65 Duration::from_secs(10)
66 }
67}
68
69impl CommandExpectedState {
70 pub fn new(cmd: &str) -> CommandExpectedState {
71 CommandExpectedState {
72 cmd: Parameter::Clear(cmd.to_string()),
73 }
74 }
75
76 pub fn new_from_secret(sec_ref: SecretReference) -> CommandExpectedState {
77 CommandExpectedState {
78 cmd: Parameter::Secret(sec_ref),
79 }
80 }
81}
82
83impl Check for CommandExpectedState {
84 fn check(&self) -> Result<(), RegentError> {
85 Ok(())
86 }
87
88 fn check_host_compatibility(
89 &self,
90 _host_properties: &HostProperties,
91 ) -> Result<(), RegentError> {
92 Ok(())
94 }
95}
96
97impl<Handler: HostHandler> AssessCompliance<Handler> for CommandExpectedState {
98 async fn assess_compliance(
99 &self,
100 _host_handler: &mut Handler,
101 host_properties: &Option<HostProperties>,
102 privilege: &Privilege,
103 _optional_secret_provider: &Option<SecretProvidersPool>,
104 ) -> Result<AttributeComplianceAssessment, RegentError> {
105 if let Some(props) = host_properties {
106 self.check_host_compatibility(props)?;
107 }
108
109 let os_kind = host_properties
111 .as_ref()
112 .map(|props| props.os_kind())
113 .unwrap_or(&OsKind::Linux(LinuxSpecifics {
114 linux_flavor: LinuxFlavor::Debian,
115 init_system: InitSystem::Systemd,
116 }));
117
118 match os_kind {
120 #[cfg(feature = "windows")]
121 OsKind::Windows(_) => {}
122 OsKind::Linux(_) => {}
123 OsKind::FreeBsd(_) => {}
124 OsKind::MacOs(_) => {}
125 OsKind::Unknown => {}
126 }
127
128 let mut remediations: Vec<Remediation> = Vec::new();
129
130 let privilege = privilege.clone();
131
132 remediations.push(Remediation::Command(CommandApiCall {
133 cmd: self.cmd.clone(),
134 privilege,
135 }));
136
137 Ok(AttributeComplianceAssessment::NonCompliant(
138 RemediationsList::from(remediations)?,
139 ))
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct CommandApiCall {
145 pub cmd: Parameter<String>,
146 privilege: Privilege,
147}
148
149impl CommandApiCall {
150 pub fn display(&self) -> String {
151 format!("Run command : {}", self.cmd)
152 }
153}
154
155impl Check for CommandApiCall {
156 fn check(&self) -> Result<(), RegentError> {
157 Ok(())
158 }
159
160 fn check_host_compatibility(
161 &self,
162 _host_properties: &HostProperties,
163 ) -> Result<(), RegentError> {
164 Ok(())
166 }
167}
168
169impl<Handler: HostHandler> ReachCompliance<Handler> for CommandApiCall {
170 async fn call(
171 &self,
172 host_handler: &mut Handler,
173 host_properties: &Option<HostProperties>,
174 optional_secret_provider: &Option<SecretProvidersPool>,
175 ) -> Result<InternalApiCallOutcome, RegentError> {
176 if let Some(props) = host_properties {
177 self.check_host_compatibility(props)?;
178 }
179
180 let os_kind = host_properties
182 .as_ref()
183 .map(|props| props.os_kind())
184 .unwrap_or(&OsKind::Linux(LinuxSpecifics {
185 linux_flavor: LinuxFlavor::Debian,
186 init_system: InitSystem::Systemd,
187 }));
188
189 let cmd_string = self
190 .cmd
191 .clone()
192 .inner_raw(optional_secret_provider)
193 .await
194 .unwrap();
195
196 match os_kind {
197 #[cfg(feature = "windows")]
198 OsKind::Windows(_) => {
199 let cmd_result = host_handler.run_windows_command(&cmd_string).await;
200
201 match cmd_result {
202 Ok(result) => {
203 if result.return_code == 0 {
204 Ok(InternalApiCallOutcome::Success(Some(result.stdout)))
205 } else {
206 Ok(InternalApiCallOutcome::Failure(format!(
207 "RC : {}, STDOUT : {}, STDERR : {}",
208 result.return_code, result.stdout, result.stderr
209 )))
210 }
211 }
212 Err(e) => Ok(InternalApiCallOutcome::Failure(format!(
213 "Command execution failed: {:?}",
214 e
215 ))),
216 }
217 }
218 OsKind::Linux(_) | OsKind::FreeBsd(_) | OsKind::MacOs(_) | OsKind::Unknown => {
219 let cmd_result = host_handler.run_command(&cmd_string, &self.privilege).await;
221
222 match cmd_result {
223 Ok(result) => {
224 if result.return_code == 0 {
225 Ok(InternalApiCallOutcome::Success(Some(result.stdout)))
226 } else {
227 Ok(InternalApiCallOutcome::Failure(format!(
228 "RC : {}, STDOUT : {}, STDERR : {}",
229 result.return_code, result.stdout, result.stderr
230 )))
231 }
232 }
233 Err(e) => Ok(InternalApiCallOutcome::Failure(format!(
234 "Command execution failed: {:?}",
235 e
236 ))),
237 }
238 }
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn parsing_service_module_block_from_yaml_str() {
249 let raw_attributes = "---
250- Cmd: ls -ltrh";
251
252 let _attributes: Vec<CommandExpectedState> = yaml_serde::from_str(raw_attributes).unwrap();
253 }
254}