use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tracing::{debug, info};
use super::error::WazuhApiError;
use super::wazuh_client::WazuhApiClient;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AgentConfiguration {
pub agent_id: String,
pub configuration: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ManagerConfiguration {
pub configuration: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GroupFilters {
pub os: Option<String>,
pub name: Option<String>,
pub profile: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GroupConfigContent {
#[serde(flatten)]
pub config: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GroupConfigurationItem {
pub filters: Option<GroupFilters>, pub config: GroupConfigContent,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GroupConfiguration {
pub group_name: String,
pub filters: Option<GroupFilters>,
pub config: Value,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ConfigurationSection {
pub section: String,
pub content: Value,
}
#[derive(Debug, Clone)]
pub struct ConfigurationClient {
api_client: WazuhApiClient,
}
impl ConfigurationClient {
pub fn new(api_client: WazuhApiClient) -> Self {
Self { api_client }
}
pub async fn get_agent_configuration(
&mut self,
agent_id: &str,
section: Option<&str>,
field: Option<&str>,
) -> Result<AgentConfiguration, WazuhApiError> {
debug!(%agent_id, ?section, ?field, "Getting agent configuration");
let mut query_params = Vec::new();
if let Some(section) = section {
query_params.push(("section", section.to_string()));
}
if let Some(field) = field {
query_params.push(("field", field.to_string()));
}
let query_params_ref: Vec<(&str, &str)> =
query_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
let endpoint = format!("/agents/{}/config", agent_id);
let response = self
.api_client
.make_request(
Method::GET,
&endpoint,
None,
if query_params_ref.is_empty() {
None
} else {
Some(&query_params_ref)
},
)
.await?;
let config_data = response
.get("data")
.and_then(|d| d.get("affected_items"))
.and_then(|items| items.as_array())
.and_then(|arr| arr.first())
.ok_or_else(|| {
WazuhApiError::ApiError(format!("Configuration for agent {} not found", agent_id))
})?;
let configuration = AgentConfiguration {
agent_id: agent_id.to_string(),
configuration: config_data.clone(),
};
info!(%agent_id, "Retrieved agent configuration");
Ok(configuration)
}
pub async fn get_manager_configuration(
&mut self,
section: Option<&str>,
field: Option<&str>,
) -> Result<ManagerConfiguration, WazuhApiError> {
debug!(?section, ?field, "Getting manager configuration");
let mut query_params = Vec::new();
if let Some(section) = section {
query_params.push(("section", section.to_string()));
}
if let Some(field) = field {
query_params.push(("field", field.to_string()));
}
let query_params_ref: Vec<(&str, &str)> =
query_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
let response = self
.api_client
.make_request(
Method::GET,
"/manager/configuration",
None,
if query_params_ref.is_empty() {
None
} else {
Some(&query_params_ref)
},
)
.await?;
let config_data = response
.get("data")
.and_then(|d| d.get("affected_items"))
.and_then(|items| items.as_array())
.and_then(|arr| arr.first())
.ok_or_else(|| {
WazuhApiError::ApiError("Manager configuration not found".to_string())
})?;
let configuration = ManagerConfiguration {
configuration: config_data.clone(),
};
info!("Retrieved manager configuration");
Ok(configuration)
}
pub async fn get_group_configuration(
&mut self,
group_name: &str,
) -> Result<GroupConfiguration, WazuhApiError> {
debug!(%group_name, "Getting group configuration");
let endpoint = format!("/agents/groups/{}/configuration", group_name);
let response = self
.api_client
.make_request(Method::GET, &endpoint, None, None)
.await?;
let config_data = response
.get("data")
.and_then(|d| d.get("affected_items"))
.and_then(|items| items.as_array())
.and_then(|arr| arr.first())
.ok_or_else(|| {
WazuhApiError::ApiError(format!("Configuration for group {} not found", group_name))
})?;
let item_config: Value = config_data
.get("config")
.cloned()
.unwrap_or_else(|| config_data.clone());
let item_filters: Option<GroupFilters> = config_data
.get("filters")
.and_then(|f| serde_json::from_value(f.clone()).ok());
let configuration = GroupConfiguration {
group_name: group_name.to_string(), filters: item_filters,
config: item_config,
};
info!(%group_name, "Retrieved group configuration");
Ok(configuration)
}
pub async fn update_group_configuration(
&mut self,
group_name: &str,
xml_configuration: String, ) -> Result<Value, WazuhApiError> {
debug!(%group_name, "Updating group configuration with XML");
let endpoint = format!("/agents/groups/{}/configuration", group_name);
let body_value = json!({ "xml_content": xml_configuration });
let response = self
.api_client
.make_request(Method::PUT, &endpoint, Some(body_value), None)
.await?;
info!(%group_name, "Updated group configuration (attempted with XML-in-JSON)");
Ok(response)
}
pub async fn get_agent_config_sections(
&mut self,
agent_id: &str,
) -> Result<Vec<String>, WazuhApiError> {
debug!(%agent_id, "Getting agent configuration sections");
let endpoint = format!("/agents/{}/config", agent_id);
let response = self
.api_client
.make_request(Method::GET, &endpoint, None, None)
.await?;
let config_data = response
.get("data")
.and_then(|d| d.get("affected_items"))
.and_then(|items| items.as_array())
.and_then(|arr| arr.first())
.ok_or_else(|| {
WazuhApiError::ApiError(format!("Configuration for agent {} not found", agent_id))
})?;
let sections: Vec<String> = if let Some(obj) = config_data.as_object() {
obj.keys().cloned().collect()
} else {
Vec::new()
};
info!(%agent_id, "Retrieved {} configuration sections", sections.len());
Ok(sections)
}
pub async fn get_manager_config_sections(&mut self) -> Result<Vec<String>, WazuhApiError> {
debug!("Getting manager configuration sections");
let response = self
.api_client
.make_request(Method::GET, "/manager/configuration", None, None)
.await?;
let config_data = response
.get("data")
.and_then(|d| d.get("affected_items"))
.and_then(|items| items.as_array())
.and_then(|arr| arr.first())
.ok_or_else(|| {
WazuhApiError::ApiError("Manager configuration not found".to_string())
})?;
let sections: Vec<String> = if let Some(obj) = config_data.as_object() {
obj.keys().cloned().collect()
} else {
Vec::new()
};
info!(
"Retrieved {} manager configuration sections",
sections.len()
);
Ok(sections)
}
pub async fn compare_agent_configurations(
&mut self,
agent_id1: &str,
agent_id2: &str,
section: Option<&str>,
) -> Result<Value, WazuhApiError> {
debug!(%agent_id1, %agent_id2, ?section, "Comparing agent configurations");
let config1 = self
.get_agent_configuration(agent_id1, section, None)
.await?;
let config2 = self
.get_agent_configuration(agent_id2, section, None)
.await?;
let comparison = json!({
"agent1": {
"id": agent_id1,
"configuration": config1.configuration
},
"agent2": {
"id": agent_id2,
"configuration": config2.configuration
},
"differences": self.find_config_differences(&config1.configuration, &config2.configuration)
});
info!(%agent_id1, %agent_id2, "Completed configuration comparison");
Ok(comparison)
}
fn find_config_differences(&self, config1: &Value, config2: &Value) -> Value {
let mut differences = Vec::new();
if let (Some(obj1), Some(obj2)) = (config1.as_object(), config2.as_object()) {
for (key, value1) in obj1 {
if let Some(value2) = obj2.get(key) {
if value1 != value2 {
differences.push(json!({
"section": key,
"agent1_value": value1,
"agent2_value": value2
}));
}
} else {
differences.push(json!({
"section": key,
"agent1_value": value1,
"agent2_value": null
}));
}
}
for (key, value2) in obj2 {
if !obj1.contains_key(key) {
differences.push(json!({
"section": key,
"agent1_value": null,
"agent2_value": value2
}));
}
}
}
json!(differences)
}
pub async fn validate_configuration(&mut self) -> Result<Value, WazuhApiError> {
debug!("Validating manager configuration");
let response = self
.api_client
.make_request(Method::GET, "/manager/configuration/validation", None, None)
.await?;
info!("Retrieved configuration validation status");
Ok(response)
}
}