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