1use reqwest::Method;
2use serde::de::{self, MapAccess, SeqAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize};
4use serde_json::Value;
5use std::fmt;
6use std::marker::PhantomData;
7use tracing::{debug, info};
8
9use super::error::WazuhApiError;
10use super::wazuh_client::WazuhApiClient;
11
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct Rule {
14 pub id: u32,
15 pub level: u32,
16 pub description: String,
17 pub filename: String,
18 pub relative_dirname: String,
19 pub status: String,
20 pub details: Option<RuleDetails>,
21 #[serde(
22 default,
23 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
24 )]
25 pub gdpr: Option<Vec<String>>,
26 #[serde(
27 default,
28 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
29 )]
30 pub gpg13: Option<Vec<String>>,
31 #[serde(
32 default,
33 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
34 )]
35 pub hipaa: Option<Vec<String>>,
36 #[serde(
37 default,
38 rename = "nist-800-53",
39 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
40 )]
41 pub nist_800_53: Option<Vec<String>>,
42 #[serde(
43 default,
44 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
45 )]
46 pub tsc: Option<Vec<String>>,
47 #[serde(
48 default,
49 rename = "pci_dss",
50 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
51 )]
52 pub pci_dss: Option<Vec<String>>,
53 #[serde(
54 default,
55 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
56 )]
57 pub mitre: Option<Vec<String>>,
58 #[serde(deserialize_with = "deserialize_vec_or_empty_map_as_vec")]
60 pub groups: Vec<String>,
61}
62
63fn deserialize_vec_or_empty_map_as_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
64where
65 D: Deserializer<'de>,
66{
67 struct VecOrEmptyMapToVecVisitor(PhantomData<Vec<String>>);
68
69 impl<'de> Visitor<'de> for VecOrEmptyMapToVecVisitor {
70 type Value = Vec<String>;
71
72 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
73 formatter.write_str("a list of strings or an empty map")
74 }
75
76 fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
77 where
78 S: SeqAccess<'de>,
79 {
80 let mut vec = Vec::new();
81 while let Some(element) = seq.next_element()? {
82 vec.push(element);
83 }
84 Ok(vec)
85 }
86
87 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
88 where
89 M: MapAccess<'de>,
90 {
91 if map.next_key::<String>()?.is_none() {
94 Ok(Vec::new()) } else {
96 Err(de::Error::invalid_type(de::Unexpected::Map, &self))
97 }
98 }
99 }
100
101 deserializer.deserialize_any(VecOrEmptyMapToVecVisitor(PhantomData))
102}
103
104fn deserialize_vec_or_empty_map_as_option_vec<'de, D>(
106 deserializer: D,
107) -> Result<Option<Vec<String>>, D::Error>
108where
109 D: Deserializer<'de>,
110{
111 struct VecOrEmptyMapVisitor(PhantomData<Option<Vec<String>>>);
112
113 impl<'de> Visitor<'de> for VecOrEmptyMapVisitor {
114 type Value = Option<Vec<String>>;
115
116 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
117 formatter.write_str("a list of strings, null, or an empty map")
118 }
119
120 fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
121 where
122 S: SeqAccess<'de>,
123 {
124 let mut vec = Vec::new();
125 while let Some(element) = seq.next_element()? {
126 vec.push(element);
127 }
128 Ok(Some(vec)) }
130
131 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
132 where
133 M: MapAccess<'de>,
134 {
135 while let Some(_key) = map.next_key::<serde_json::Value>()? {
141 let _ = map.next_value::<serde_json::Value>()?;
143 }
144 Ok(None) }
146
147 fn visit_none<E>(self) -> Result<Self::Value, E>
148 where
149 E: de::Error,
150 {
151 Ok(None) }
153
154 fn visit_unit<E>(self) -> Result<Self::Value, E>
155 where
156 E: de::Error,
157 {
158 Ok(None) }
160 }
161
162 deserializer.deserialize_any(VecOrEmptyMapVisitor(PhantomData))
163}
164
165#[derive(Debug, Clone, Deserialize, Serialize)]
166pub struct PatternDetail {
167 pub pattern: String,
168}
169
170#[derive(Debug, Clone, Deserialize, Serialize)]
171pub struct InfoDetail {
172 pub name: String,
173 #[serde(rename = "type")]
174 pub type_info: String,
175}
176
177fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Option<Vec<String>>, D::Error>
178where
179 D: Deserializer<'de>,
180{
181 struct StringOrVec(PhantomData<Vec<String>>);
182
183 impl<'de> Visitor<'de> for StringOrVec {
184 type Value = Option<Vec<String>>;
185
186 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
187 formatter.write_str("string or list of strings")
188 }
189
190 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
191 where
192 E: de::Error,
193 {
194 if value.is_empty() {
195 Ok(None) } else {
197 Ok(Some(vec![value.to_owned()]))
198 }
199 }
200
201 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
202 where
203 E: de::Error,
204 {
205 if value.is_empty() {
206 Ok(None)
207 } else {
208 Ok(Some(vec![value]))
209 }
210 }
211
212 fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
213 where
214 S: SeqAccess<'de>,
215 {
216 let mut vec = Vec::new();
217 while let Some(element) = seq.next_element()? {
218 vec.push(element);
219 }
220 if vec.is_empty() {
221 Ok(None)
222 } else {
223 Ok(Some(vec))
224 }
225 }
226
227 fn visit_none<E>(self) -> Result<Self::Value, E>
228 where
229 E: de::Error,
230 {
231 Ok(None)
232 }
233
234 fn visit_unit<E>(self) -> Result<Self::Value, E>
236 where
237 E: de::Error,
238 {
239 Ok(None)
240 }
241 }
242
243 deserializer.deserialize_any(StringOrVec(PhantomData))
244}
245
246fn deserialize_empty_string_as_none_bool<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
247where
248 D: Deserializer<'de>,
249{
250 struct EmptyStringAsNoneBool(PhantomData<Option<bool>>);
251
252 impl<'de> Visitor<'de> for EmptyStringAsNoneBool {
253 type Value = Option<bool>;
254
255 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
256 formatter.write_str("a boolean or an empty string")
257 }
258
259 fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
260 where
261 E: de::Error,
262 {
263 Ok(Some(value))
264 }
265
266 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
267 where
268 E: de::Error,
269 {
270 if value.is_empty() {
271 Ok(None)
272 } else {
273 match value.to_lowercase().as_str() {
275 "true" => Ok(Some(true)),
276 "false" => Ok(Some(false)),
277 _ => Err(de::Error::invalid_value(de::Unexpected::Str(value), &self)),
278 }
279 }
280 }
281
282 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
283 where
284 E: de::Error,
285 {
286 if value.is_empty() {
287 Ok(None)
288 } else {
289 match value.to_lowercase().as_str() {
290 "true" => Ok(Some(true)),
291 "false" => Ok(Some(false)),
292 _ => Err(de::Error::invalid_value(de::Unexpected::Str(&value), &self)),
293 }
294 }
295 }
296
297 fn visit_none<E>(self) -> Result<Self::Value, E>
298 where
299 E: de::Error,
300 {
301 Ok(None)
302 }
303
304 fn visit_unit<E>(self) -> Result<Self::Value, E>
305 where
306 E: de::Error,
307 {
308 Ok(None) }
310 }
311
312 deserializer.deserialize_any(EmptyStringAsNoneBool(PhantomData))
313}
314
315#[derive(Debug, Clone, Deserialize, Serialize)]
316pub struct RuleDetails {
317 pub category: Option<String>,
318 pub if_sid: Option<String>,
319 #[serde(default, deserialize_with = "deserialize_string_or_vec")]
320 pub if_group: Option<Vec<String>>,
321 #[serde(rename = "match")]
322 pub match_obj: Option<PatternDetail>,
323 #[serde(rename = "regex")]
324 pub regex_obj: Option<PatternDetail>,
325 pub order: Option<String>,
326 pub frequency: Option<String>,
327 pub timeframe: Option<String>,
328 pub ignore: Option<String>,
329 #[serde(default, deserialize_with = "deserialize_empty_string_as_none_bool")]
330 pub check_diff: Option<bool>,
331 #[serde(
332 default,
333 deserialize_with = "deserialize_vec_or_empty_map_as_option_vec"
334 )]
335 pub group: Option<Vec<String>>,
336 pub info: Option<Value>,
337 #[serde(default, deserialize_with = "deserialize_string_or_vec")]
338 pub options: Option<Vec<String>>,
339 #[serde(rename = "level")]
340 pub level_detail: Option<PatternDetail>,
341 pub alert_type: Option<PatternDetail>,
342 pub fim_db_table: Option<PatternDetail>,
343 pub status: Option<PatternDetail>,
344 pub action: Option<PatternDetail>,
345 #[serde(rename = "id")]
346 pub id_detail: Option<PatternDetail>,
347 #[serde(rename = "cisco.severity")]
348 pub cisco_severity_detail: Option<PatternDetail>,
349 #[serde(rename = "win.system.severityValue")]
350 pub win_system_severity_value_detail: Option<PatternDetail>,
351 #[serde(rename = "win.eventdata.targetSid")]
352 pub win_eventdata_target_sid_detail: Option<PatternDetail>,
353 #[serde(rename = "win.eventdata.failureCode")]
354 pub win_eventdata_failure_code_detail: Option<PatternDetail>,
355 #[serde(rename = "win.eventdata.image")]
356 pub win_eventdata_image_detail: Option<PatternDetail>,
357 #[serde(rename = "win.eventdata.parentImage")]
358 pub win_eventdata_parent_image_detail: Option<PatternDetail>,
359 #[serde(rename = "win.eventdata.originalFileName")]
360 pub win_eventdata_original_filename_detail: Option<PatternDetail>,
361 #[serde(rename = "win.eventdata.commandLine")]
362 pub win_eventdata_commandline_detail: Option<PatternDetail>,
363 #[serde(rename = "win.eventdata.type")]
364 pub win_eventdata_type_detail: Option<PatternDetail>,
365 #[serde(rename = "win.eventdata.destination")]
366 pub win_eventdata_destination_detail: Option<PatternDetail>,
367 #[serde(rename = "win.system.message")]
368 pub win_system_message_detail: Option<PatternDetail>,
369 #[serde(rename = "win.eventdata.scriptBlockText")]
370 pub win_eventdata_scriptblocktext_detail: Option<PatternDetail>,
371 #[serde(rename = "Severity")]
372 pub severity_detail: Option<PatternDetail>,
373 pub appcat: Option<PatternDetail>,
374 pub pri: Option<PatternDetail>,
375 #[serde(rename = "audit.type")]
376 pub audit_type_detail: Option<PatternDetail>,
377 #[serde(rename = "audit.res")]
378 pub audit_res_detail: Option<PatternDetail>,
379 #[serde(rename = "event.code")]
380 pub event_code_detail: Option<PatternDetail>,
381 #[serde(rename = "cs4Label")]
382 pub cs4label_detail: Option<PatternDetail>,
383 #[serde(rename = "cs4")]
384 pub cs4_detail: Option<PatternDetail>,
385 #[serde(rename = "cn3Label")]
386 pub cn3label_detail: Option<PatternDetail>,
387 #[serde(rename = "cn3")]
388 pub cn3_detail: Option<PatternDetail>,
389 #[serde(rename = "office365.RecordType")]
390 pub office365_recordtype_detail: Option<PatternDetail>,
391 #[serde(rename = "office365.Operation")]
392 pub office365_operation_detail: Option<PatternDetail>,
393 #[serde(rename = "office365.Parameters")]
394 pub office365_parameters_detail: Option<PatternDetail>,
395 #[serde(rename = "github.action")]
396 pub github_action_detail: Option<PatternDetail>,
397 #[serde(rename = "vuls.score")]
398 pub vuls_score_detail: Option<PatternDetail>,
399 #[serde(rename = "vulnerability.status")]
400 pub vulnerability_status_detail: Option<PatternDetail>,
401 #[serde(rename = "vulnerability.severity")]
402 pub vulnerability_severity_detail: Option<PatternDetail>,
403 #[serde(rename = "qualysguard.severity")]
404 pub qualysguard_severity_detail: Option<PatternDetail>,
405 #[serde(rename = "virustotal.malicious")]
406 pub virustotal_malicious_detail: Option<PatternDetail>,
407}
408
409#[derive(Debug, Clone, Deserialize, Serialize)]
410pub struct Decoder {
411 pub name: String,
412 pub filename: String,
413 pub relative_dirname: String,
414 pub status: String,
415 pub position: Option<u32>,
416 pub details: Option<DecoderDetails>,
417}
418
419#[derive(Debug, Clone, Deserialize, Serialize)]
420pub struct DecoderPatternDetail {
421 pub pattern: String,
422 pub offset: Option<String>,
423 #[serde(rename = "type")]
424 pub type_info: Option<String>,
425}
426
427#[derive(Debug, Clone, Deserialize, Serialize)]
428pub struct DecoderDetails {
429 pub program_name: Option<String>,
430 pub order: Option<String>,
431 pub prematch: Option<DecoderPatternDetail>,
432 pub regex: Option<DecoderPatternDetail>,
433 pub parent: Option<String>,
434 pub use_own_name: Option<bool>,
435 pub json_null_field: Option<String>,
436 pub plugin_decoder: Option<String>,
437}
438
439#[derive(Debug, Clone)]
440pub struct RulesClient {
441 api_client: WazuhApiClient,
442}
443
444impl RulesClient {
445 pub fn new(api_client: WazuhApiClient) -> Self {
446 Self { api_client }
447 }
448
449 pub async fn get_rules(
450 &mut self,
451 limit: Option<u32>,
452 offset: Option<u32>,
453 level: Option<u32>,
454 group: Option<&str>,
455 filename: Option<&str>,
456 ) -> Result<Vec<Rule>, WazuhApiError> {
457 debug!("Getting rules list");
458
459 let mut query_params = Vec::new();
460
461 if let Some(limit) = limit {
462 query_params.push(("limit", limit.to_string()));
463 }
464 if let Some(offset) = offset {
465 query_params.push(("offset", offset.to_string()));
466 }
467 if let Some(level) = level {
468 query_params.push(("level", level.to_string()));
469 }
470 if let Some(group) = group {
471 query_params.push(("group", group.to_string()));
472 }
473 if let Some(filename) = filename {
474 query_params.push(("filename", filename.to_string()));
475 }
476
477 let query_params_ref: Vec<(&str, &str)> =
478 query_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
479
480 let response = self
481 .api_client
482 .make_request(
483 Method::GET,
484 "/rules",
485 None,
486 if query_params_ref.is_empty() {
487 None
488 } else {
489 Some(&query_params_ref)
490 },
491 )
492 .await?;
493
494 let rules_data = response
495 .get("data")
496 .and_then(|d| d.get("affected_items"))
497 .ok_or_else(|| {
498 WazuhApiError::ApiError(
499 "Missing 'data.affected_items' in rules response".to_string(),
500 )
501 })?;
502
503 let rules: Vec<Rule> = serde_json::from_value(rules_data.clone())?;
504 info!("Retrieved {} rules", rules.len());
505 Ok(rules)
506 }
507
508 pub async fn get_rule(&mut self, rule_id: u32) -> Result<Rule, WazuhApiError> {
509 debug!(%rule_id, "Getting specific rule");
510
511 let rule_id_str = rule_id.to_string();
512 let query_params = [("rule_ids", rule_id_str.as_str())];
513 let response = self
514 .api_client
515 .make_request(Method::GET, "/rules", None, Some(&query_params))
516 .await?;
517
518 let rule_data = response
519 .get("data")
520 .and_then(|d| d.get("affected_items"))
521 .and_then(|items| items.as_array())
522 .and_then(|arr| arr.first())
523 .ok_or_else(|| WazuhApiError::ApiError(format!("Rule {} not found", rule_id)))?;
524
525 let rule: Rule = serde_json::from_value(rule_data.clone())?;
526 info!(%rule_id, "Retrieved rule details");
527 Ok(rule)
528 }
529
530 pub async fn get_rules_by_level(&mut self, level: u32) -> Result<Vec<Rule>, WazuhApiError> {
531 debug!(%level, "Getting rules by level");
532 self.get_rules(None, None, Some(level), None, None).await
533 }
534
535 pub async fn get_rules_by_group(&mut self, group: &str) -> Result<Vec<Rule>, WazuhApiError> {
536 debug!(%group, "Getting rules by group");
537 self.get_rules(None, None, None, Some(group), None).await
538 }
539
540 pub async fn get_high_level_rules(&mut self) -> Result<Vec<Rule>, WazuhApiError> {
541 debug!("Getting high-level rules");
542
543 let query_params = [("level", "10-15")];
544 let response = self
545 .api_client
546 .make_request(Method::GET, "/rules", None, Some(&query_params))
547 .await?;
548
549 let rules_data = response
550 .get("data")
551 .and_then(|d| d.get("affected_items"))
552 .ok_or_else(|| {
553 WazuhApiError::ApiError(
554 "Missing 'data.affected_items' in high-level rules response".to_string(),
555 )
556 })?;
557
558 let rules: Vec<Rule> = serde_json::from_value(rules_data.clone())?;
559 info!("Retrieved {} high-level rules", rules.len());
560 Ok(rules)
561 }
562
563 pub async fn get_rule_groups(&mut self) -> Result<Vec<String>, WazuhApiError> {
564 debug!("Getting rule groups");
565
566 let response = self
567 .api_client
568 .make_request(Method::GET, "/rules/groups", None, None)
569 .await?;
570
571 let groups_data = response
572 .get("data")
573 .and_then(|d| d.get("affected_items"))
574 .ok_or_else(|| {
575 WazuhApiError::ApiError(
576 "Missing 'data.affected_items' in rule groups response".to_string(),
577 )
578 })?;
579
580 let groups: Vec<String> = serde_json::from_value(groups_data.clone())?;
581 info!("Retrieved {} rule groups", groups.len());
582 Ok(groups)
583 }
584
585 pub async fn get_decoders(
586 &mut self,
587 limit: Option<u32>,
588 offset: Option<u32>,
589 filename: Option<&str>,
590 ) -> Result<Vec<Decoder>, WazuhApiError> {
591 debug!("Getting decoders list");
592
593 let mut query_params = Vec::new();
594
595 if let Some(limit) = limit {
596 query_params.push(("limit", limit.to_string()));
597 }
598 if let Some(offset) = offset {
599 query_params.push(("offset", offset.to_string()));
600 }
601 if let Some(filename) = filename {
602 query_params.push(("filename", filename.to_string()));
603 }
604
605 let query_params_ref: Vec<(&str, &str)> =
606 query_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
607
608 let response = self
609 .api_client
610 .make_request(
611 Method::GET,
612 "/decoders",
613 None,
614 if query_params_ref.is_empty() {
615 None
616 } else {
617 Some(&query_params_ref)
618 },
619 )
620 .await?;
621
622 let decoders_data = response
623 .get("data")
624 .and_then(|d| d.get("affected_items"))
625 .ok_or_else(|| {
626 WazuhApiError::ApiError(
627 "Missing 'data.affected_items' in decoders response".to_string(),
628 )
629 })?;
630
631 let decoders: Vec<Decoder> = serde_json::from_value(decoders_data.clone())?;
632 info!("Retrieved {} decoders", decoders.len());
633 Ok(decoders)
634 }
635
636 pub async fn get_decoder(&mut self, decoder_name: &str) -> Result<Decoder, WazuhApiError> {
637 debug!(%decoder_name, "Getting specific decoder");
638
639 let query_params = [("decoder_names", decoder_name)];
640 let response = self
641 .api_client
642 .make_request(Method::GET, "/decoders", None, Some(&query_params))
643 .await?;
644
645 let decoder_data = response
646 .get("data")
647 .and_then(|d| d.get("affected_items"))
648 .and_then(|items| items.as_array())
649 .and_then(|arr| arr.first())
650 .ok_or_else(|| {
651 WazuhApiError::ApiError(format!("Decoder {} not found", decoder_name))
652 })?;
653
654 let decoder: Decoder = serde_json::from_value(decoder_data.clone())?;
655 info!(%decoder_name, "Retrieved decoder details");
656 Ok(decoder)
657 }
658
659 pub async fn search_rules(&mut self, search_term: &str) -> Result<Vec<Rule>, WazuhApiError> {
660 debug!(%search_term, "Searching rules by description");
661
662 let query_params = [("search", search_term)];
663 let response = self
664 .api_client
665 .make_request(Method::GET, "/rules", None, Some(&query_params))
666 .await?;
667
668 let rules_data = response
669 .get("data")
670 .and_then(|d| d.get("affected_items"))
671 .ok_or_else(|| {
672 WazuhApiError::ApiError(
673 "Missing 'data.affected_items' in search rules response".to_string(),
674 )
675 })?;
676
677 let rules: Vec<Rule> = serde_json::from_value(rules_data.clone())?;
678 info!(%search_term, "Found {} rules matching search", rules.len());
679 Ok(rules)
680 }
681}