wazuh-client 0.1.8

A Rust client library for interacting with Wazuh API and Indexer
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{debug, info};

use super::error::WazuhApiError;
use super::wazuh_client::WazuhApiClient;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClusterStatus {
    pub enabled: String,
    pub running: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClusterNode {
    pub name: String,
    pub node_type: String,
    pub version: String,
    pub ip: String,
    pub status: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ManagerStatus {
    pub wazuh_version: String,
    pub openssl_version: String,
    pub compilation_date: String,
    pub version: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ProcessStatus {
    #[serde(rename = "wazuh-agentlessd")]
    pub wazuh_agentlessd: String,
    #[serde(rename = "wazuh-analysisd")]
    pub wazuh_analysisd: String,
    #[serde(rename = "wazuh-authd")]
    pub wazuh_authd: String,
    #[serde(rename = "wazuh-csyslogd")]
    pub wazuh_csyslogd: String,
    #[serde(rename = "wazuh-dbd")]
    pub wazuh_dbd: String,
    #[serde(rename = "wazuh-monitord")]
    pub wazuh_monitord: String,
    #[serde(rename = "wazuh-execd")]
    pub wazuh_execd: String,
    #[serde(rename = "wazuh-integratord")]
    pub wazuh_integratord: String,
    #[serde(rename = "wazuh-logcollector")]
    pub wazuh_logcollector: String,
    #[serde(rename = "wazuh-maild")]
    pub wazuh_maild: String,
    #[serde(rename = "wazuh-remoted")]
    pub wazuh_remoted: String,
    #[serde(rename = "wazuh-reportd")]
    pub wazuh_reportd: String,
    #[serde(rename = "wazuh-syscheckd")]
    pub wazuh_syscheckd: String,
    #[serde(rename = "wazuh-clusterd")]
    pub wazuh_clusterd: String,
    #[serde(rename = "wazuh-modulesd")]
    pub wazuh_modulesd: String,
    #[serde(rename = "wazuh-db")]
    pub wazuh_db: String,
    #[serde(rename = "wazuh-apid")]
    pub wazuh_apid: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ManagerInfo {
    pub path: String,
    pub version: String,
    #[serde(rename = "type")]
    pub node_type: String,
    pub max_agents: String,
    pub openssl_support: Option<String>,
    pub tz_offset: Option<String>,
    pub tz_name: Option<String>,
    pub installation_date: Option<String>,
    pub revision: Option<String>,
    pub license_version: Option<String>,
    pub license_path: Option<String>,
    pub home_path: Option<String>,
    pub share_path: Option<String>,
    pub openssl_version: Option<String>,
    pub node_name: Option<String>,
    pub cluster_name: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClusterHealthcheck {
    pub nodes: Vec<ClusterNodeHealth>,
    pub n_connected_nodes: u32,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClusterNodeHealth {
    pub info: ClusterNodeInfo,
    pub status: ClusterNodeStatus,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClusterNodeInfo {
    pub name: String,
    pub node_type: String,
    pub version: String,
    pub ip: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ClusterNodeStatus {
    pub last_keep_alive: String,
    pub sync_integrity_free: bool,
    pub sync_agent_info_free: bool,
    pub sync_extravalid_free: bool,
}

#[derive(Debug, Clone)]
pub struct ClusterClient {
    api_client: WazuhApiClient,
}

impl ClusterClient {
    pub fn new(api_client: WazuhApiClient) -> Self {
        Self { api_client }
    }

    pub async fn get_cluster_status(&mut self) -> Result<ClusterStatus, WazuhApiError> {
        debug!("Getting cluster status");

        let response = self
            .api_client
            .make_request(Method::GET, "/cluster/status", None, None)
            .await?;

        let status_data = response.get("data").ok_or_else(|| {
            WazuhApiError::ApiError("Missing 'data' in cluster status response".to_string())
        })?;

        let status: ClusterStatus = serde_json::from_value(status_data.clone())?;
        info!(
            "Retrieved cluster status: enabled={}, running={}",
            status.enabled, status.running
        );
        Ok(status)
    }

    pub async fn get_cluster_nodes(
        &mut self,
        limit: Option<u32>,
        offset: Option<u32>,
        node_type: Option<&str>,
    ) -> Result<Vec<ClusterNode>, WazuhApiError> {
        debug!(?node_type, "Getting cluster nodes");

        let mut query_params = Vec::new();

        if let Some(limit) = limit {
            query_params.push(("limit", limit.to_string()));
        }
        if let Some(offset) = offset {
            query_params.push(("offset", offset.to_string()));
        }
        if let Some(node_type) = node_type {
            query_params.push(("type", node_type.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,
                "/cluster/nodes",
                None,
                if query_params_ref.is_empty() {
                    None
                } else {
                    Some(&query_params_ref)
                },
            )
            .await?;

        let nodes_data = response
            .get("data")
            .and_then(|d| d.get("affected_items"))
            .ok_or_else(|| {
                WazuhApiError::ApiError(
                    "Missing 'data.affected_items' in cluster nodes response".to_string(),
                )
            })?;

        let nodes: Vec<ClusterNode> = serde_json::from_value(nodes_data.clone())?;
        info!("Retrieved {} cluster nodes", nodes.len());
        Ok(nodes)
    }

    pub async fn get_cluster_node(
        &mut self,
        node_name: &str,
    ) -> Result<ClusterNode, WazuhApiError> {
        debug!(%node_name, "Getting specific cluster node");

        let endpoint = format!("/cluster/nodes/{}", node_name);
        let response = self
            .api_client
            .make_request(Method::GET, &endpoint, None, None)
            .await?;

        let node_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!("Cluster node {} not found", node_name))
            })?;

        let node: ClusterNode = serde_json::from_value(node_data.clone())?;
        info!(%node_name, "Retrieved cluster node details");
        Ok(node)
    }

    pub async fn get_cluster_healthcheck(&mut self) -> Result<ClusterHealthcheck, WazuhApiError> {
        debug!("Getting cluster healthcheck");

        let response = self
            .api_client
            .make_request(Method::GET, "/cluster/healthcheck", None, None)
            .await?;

        let healthcheck_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("Missing cluster healthcheck data".to_string())
            })?;

        let healthcheck: ClusterHealthcheck = serde_json::from_value(healthcheck_data.clone())?;
        info!(
            "Retrieved cluster healthcheck: {} connected nodes",
            healthcheck.n_connected_nodes
        );
        Ok(healthcheck)
    }

    pub async fn get_manager_process_status(&mut self) -> Result<ProcessStatus, WazuhApiError> {
        debug!("Getting manager process status");

        let response = self
            .api_client
            .make_request(Method::GET, "/manager/status", None, None)
            .await?;

        let status_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("Missing manager process status data".to_string())
            })?;

        let status: ProcessStatus = serde_json::from_value(status_data.clone())?;
        info!("Retrieved manager process status");
        Ok(status)
    }

    pub async fn get_manager_status(&mut self) -> Result<ManagerStatus, WazuhApiError> {
        debug!("Getting manager status");

        let response = self
            .api_client
            .make_request(Method::GET, "/manager/info", None, None)
            .await?;

        let status_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("Missing manager status data".to_string()))?;

        let manager_info: ManagerInfo = serde_json::from_value(status_data.clone())?;

        let status = ManagerStatus {
            wazuh_version: manager_info.version.clone(),
            // Ensure ManagerInfo.openssl_version is what's needed or adjust source
            openssl_version: manager_info.openssl_version.unwrap_or_default(),
            // Ensure ManagerInfo.installation_date is what's needed or adjust source
            compilation_date: manager_info.installation_date.unwrap_or_default(),
            version: manager_info.version,
        };

        info!("Retrieved manager status: version={}", status.wazuh_version);
        Ok(status)
    }

    pub async fn get_manager_info(&mut self) -> Result<ManagerInfo, WazuhApiError> {
        debug!("Getting manager information");

        let response = self
            .api_client
            .make_request(Method::GET, "/manager/info", None, None)
            .await?;

        let info_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("Missing manager info data".to_string()))?;

        let info: ManagerInfo = serde_json::from_value(info_data.clone())?;
        info!(
            "Retrieved manager info: version={}, node_name={}",
            info.version,
            info.node_name.as_deref().unwrap_or("unknown")
        );
        Ok(info)
    }

    pub async fn get_cluster_configuration(&mut self) -> Result<Value, WazuhApiError> {
        debug!("Getting cluster configuration");

        let response = self
            .api_client
            .make_request(Method::GET, "/cluster/configuration", None, None)
            .await?;

        info!("Retrieved cluster configuration");
        Ok(response)
    }

    pub async fn get_master_nodes(&mut self) -> Result<Vec<ClusterNode>, WazuhApiError> {
        debug!("Getting master nodes");
        self.get_cluster_nodes(None, None, Some("master")).await
    }

    pub async fn get_worker_nodes(&mut self) -> Result<Vec<ClusterNode>, WazuhApiError> {
        debug!("Getting worker nodes");
        self.get_cluster_nodes(None, None, Some("worker")).await
    }

    pub async fn is_cluster_healthy(&mut self) -> Result<bool, WazuhApiError> {
        debug!("Checking cluster health");

        let status = self.get_cluster_status().await?;
        let is_enabled = status.enabled.eq_ignore_ascii_case("yes");
        let is_running = status.running.eq_ignore_ascii_case("yes");
        let is_healthy = is_enabled && is_running;

        if is_healthy {
            // Additional check: verify nodes are connected
            match self.get_cluster_healthcheck().await {
                Ok(healthcheck) => {
                    let healthy = healthcheck.n_connected_nodes > 0;
                    info!("Cluster health check: enabled={}, running={}, connected_nodes={}, healthy={}", 
                          is_enabled, is_running, healthcheck.n_connected_nodes, healthy);
                    Ok(healthy)
                }
                Err(_) => {
                    info!(
                        "Cluster health check: enabled={}, running={}, healthcheck_failed=true",
                        is_enabled, is_running
                    );
                    Ok(false) // Or handle error more explicitly if healthcheck failure means unhealthy
                }
            }
        } else {
            info!(
                "Cluster health check: enabled={}, running={}, healthy=false",
                is_enabled, is_running
            );
            Ok(false)
        }
    }

    pub async fn get_cluster_statistics(&mut self) -> Result<Value, WazuhApiError> {
        debug!("Getting cluster statistics");

        let response = self
            .api_client
            .make_request(Method::GET, "/cluster/stats", None, None)
            .await?;

        info!("Retrieved cluster statistics");
        Ok(response)
    }

    pub async fn get_local_node_info(&mut self) -> Result<Value, WazuhApiError> {
        debug!("Getting local node information");

        let response = self
            .api_client
            .make_request(Method::GET, "/cluster/local/info", None, None)
            .await?;

        info!("Retrieved local node information");
        Ok(response)
    }

    pub async fn restart_manager(&mut self) -> Result<Value, WazuhApiError> {
        debug!("Restarting manager");

        let response = self
            .api_client
            .make_request(Method::PUT, "/manager/restart", None, None)
            .await?;

        info!("Manager restart command sent");
        Ok(response)
    }

    pub async fn get_manager_logs_summary(&mut self) -> Result<Value, WazuhApiError> {
        debug!("Getting manager logs summary");

        let response = self
            .api_client
            .make_request(Method::GET, "/manager/logs/summary", None, None)
            .await?;

        info!("Retrieved manager logs summary");
        Ok(response)
    }
}