vector/sources/windows_event_log/
config.rs1use std::{collections::HashMap, path::PathBuf};
2
3use vector_config::component::GenerateConfig;
4use vector_lib::configurable::configurable_component;
5
6use crate::{config::SourceAcknowledgementsConfig, serde::bool_or_struct};
7
8const MAX_CHANNEL_NAME_LENGTH: usize = 256;
10const MAX_XPATH_QUERY_LENGTH: usize = 4096;
11const MAX_FIELD_NAME_LENGTH: usize = 128;
12const MAX_FIELD_COUNT: usize = 100;
13const MAX_EVENT_ID_LIST_SIZE: usize = 1000;
14const MAX_CHANNELS: usize = 63; const MAX_CONNECTION_TIMEOUT_SECS: u64 = 3600;
16const MAX_EVENT_TIMEOUT_MS: u64 = 60000;
17const MAX_BATCH_SIZE: u32 = 10000;
18
19#[configurable_component(source(
21 "windows_event_log",
22 "Collect logs from Windows Event Log channels using the Windows Event Log API."
23))]
24#[derive(Clone, Debug)]
25#[serde(deny_unknown_fields)]
26pub struct WindowsEventLogConfig {
27 #[configurable(metadata(docs::examples = "System,Application,Security"))]
32 #[configurable(metadata(docs::examples = "System"))]
33 pub channels: Vec<String>,
34
35 #[configurable(metadata(docs::examples = "*[System[Level=1 or Level=2 or Level=3]]"))]
40 #[configurable(metadata(
41 docs::examples = "*[System[(Level=1 or Level=2 or Level=3) and TimeCreated[timediff(@SystemTime) <= 86400000]]]"
42 ))]
43 pub event_query: Option<String>,
44
45 #[serde(default = "default_connection_timeout_secs")]
49 #[configurable(metadata(docs::examples = 30))]
50 #[configurable(metadata(docs::examples = 60))]
51 pub connection_timeout_secs: u64,
52
53 #[serde(default = "default_read_existing_events")]
58 pub read_existing_events: bool,
59
60 #[serde(default = "default_batch_size")]
64 #[configurable(metadata(docs::examples = 10))]
65 #[configurable(metadata(docs::examples = 100))]
66 pub batch_size: u32,
67
68 #[serde(default = "default_include_xml")]
73 pub include_xml: bool,
74
75 #[serde(default)]
79 #[configurable(metadata(
80 docs::additional_props_description = "An individual event data format override."
81 ))]
82 pub event_data_format: HashMap<String, EventDataFormat>,
83
84 #[serde(default)]
88 #[configurable(metadata(docs::examples = 4624))]
89 #[configurable(metadata(docs::examples = 4625))]
90 #[configurable(metadata(docs::examples = 4634))]
91 pub ignore_event_ids: Vec<u32>,
92
93 #[configurable(metadata(docs::examples = 1000))]
98 #[configurable(metadata(docs::examples = 1001))]
99 #[configurable(metadata(docs::examples = 1002))]
100 pub only_event_ids: Option<Vec<u32>>,
101
102 #[configurable(metadata(docs::examples = 86400))]
107 #[configurable(metadata(docs::examples = 604800))]
108 pub max_event_age_secs: Option<u64>,
109
110 #[serde(default = "default_event_timeout_ms")]
116 #[configurable(metadata(docs::examples = 5000))]
117 #[configurable(metadata(docs::examples = 10000))]
118 pub event_timeout_ms: u64,
119
120 #[configurable(metadata(docs::hidden))]
122 #[serde(default)]
123 pub log_namespace: Option<bool>,
124
125 #[serde(default)]
129 pub field_filter: FieldFilter,
130
131 #[serde(default)]
138 #[configurable(metadata(docs::examples = "/var/lib/vector"))]
139 #[configurable(metadata(docs::examples = "C:\\ProgramData\\vector"))]
140 #[configurable(metadata(docs::human_name = "Data Directory"))]
141 pub data_dir: Option<PathBuf>,
142
143 #[serde(default = "default_events_per_second")]
149 #[configurable(metadata(docs::examples = 100))]
150 #[configurable(metadata(docs::examples = 1000))]
151 #[configurable(metadata(docs::examples = 5000))]
152 pub events_per_second: u32,
153
154 #[serde(default = "default_max_event_data_length")]
159 #[configurable(metadata(docs::examples = 1024))]
160 #[configurable(metadata(docs::examples = 4096))]
161 pub max_event_data_length: usize,
162
163 #[serde(default = "default_checkpoint_interval_secs")]
169 #[configurable(metadata(docs::examples = 5))]
170 #[configurable(metadata(docs::examples = 1))]
171 #[configurable(metadata(docs::examples = 30))]
172 pub checkpoint_interval_secs: u64,
173
174 #[serde(default = "default_render_message")]
184 pub render_message: bool,
185
186 #[configurable(derived)]
197 #[serde(default, deserialize_with = "bool_or_struct")]
198 pub acknowledgements: SourceAcknowledgementsConfig,
199}
200
201#[configurable_component]
206#[derive(Clone, Debug)]
207#[serde(rename_all = "snake_case")]
208pub enum EventDataFormat {
209 String,
211 Integer,
213 Float,
215 Boolean,
218 Auto,
221}
222
223#[configurable_component]
225#[derive(Clone, Debug)]
226pub struct FieldFilter {
227 pub include_fields: Option<Vec<String>>,
231
232 pub exclude_fields: Option<Vec<String>>,
236
237 #[serde(default = "default_include_system_fields")]
241 pub include_system_fields: bool,
242
243 #[serde(default = "default_include_event_data")]
247 pub include_event_data: bool,
248
249 #[serde(default = "default_include_user_data")]
253 pub include_user_data: bool,
254}
255
256impl Default for FieldFilter {
257 fn default() -> Self {
258 Self {
259 include_fields: None,
260 exclude_fields: None,
261 include_system_fields: default_include_system_fields(),
262 include_event_data: default_include_event_data(),
263 include_user_data: default_include_user_data(),
264 }
265 }
266}
267
268impl Default for WindowsEventLogConfig {
269 fn default() -> Self {
270 Self {
271 channels: vec!["System".to_string(), "Application".to_string()],
272 event_query: None,
273 connection_timeout_secs: default_connection_timeout_secs(),
274 read_existing_events: default_read_existing_events(),
275 batch_size: default_batch_size(),
276 include_xml: default_include_xml(),
277 event_data_format: HashMap::new(),
278 ignore_event_ids: Vec::new(),
279 only_event_ids: None,
280 max_event_age_secs: None,
281 event_timeout_ms: default_event_timeout_ms(),
282 log_namespace: None,
283 field_filter: FieldFilter::default(),
284 data_dir: None,
285 events_per_second: default_events_per_second(),
286 max_event_data_length: default_max_event_data_length(),
287 checkpoint_interval_secs: default_checkpoint_interval_secs(),
288 render_message: default_render_message(),
289 acknowledgements: Default::default(),
290 }
291 }
292}
293
294impl GenerateConfig for WindowsEventLogConfig {
295 fn generate_config() -> toml::Value {
296 toml::Value::try_from(WindowsEventLogConfig::default()).unwrap()
297 }
298}
299
300impl WindowsEventLogConfig {
301 pub fn validate(&self) -> Result<(), crate::Error> {
303 if self.channels.is_empty() {
304 return Err("At least one channel must be specified".into());
305 }
306
307 if self.channels.len() > MAX_CHANNELS {
310 return Err(format!(
311 "Too many channels: {} specified, maximum is {} \
312 (limited by WaitForMultipleObjects)",
313 self.channels.len(),
314 MAX_CHANNELS
315 )
316 .into());
317 }
318
319 if self.connection_timeout_secs == 0
321 || self.connection_timeout_secs > MAX_CONNECTION_TIMEOUT_SECS
322 {
323 return Err(format!(
324 "Connection timeout must be between 1 and {} seconds",
325 MAX_CONNECTION_TIMEOUT_SECS
326 )
327 .into());
328 }
329
330 if self.event_timeout_ms == 0 || self.event_timeout_ms > MAX_EVENT_TIMEOUT_MS {
332 return Err(format!(
333 "Event timeout must be between 1 and {} milliseconds",
334 MAX_EVENT_TIMEOUT_MS
335 )
336 .into());
337 }
338
339 if self.checkpoint_interval_secs == 0 || self.checkpoint_interval_secs > 3600 {
341 return Err("Checkpoint interval must be between 1 and 3600 seconds".into());
342 }
343
344 if self.batch_size == 0 || self.batch_size > MAX_BATCH_SIZE {
346 return Err(format!("Batch size must be between 1 and {}", MAX_BATCH_SIZE).into());
347 }
348
349 for channel in &self.channels {
351 if channel.trim().is_empty() {
352 return Err("Channel names cannot be empty".into());
353 }
354
355 if channel.len() > MAX_CHANNEL_NAME_LENGTH {
357 return Err(format!(
358 "Channel name '{}' exceeds maximum length of {} characters",
359 channel, MAX_CHANNEL_NAME_LENGTH
360 )
361 .into());
362 }
363
364 if is_channel_pattern(channel) {
366 return Err(format!(
367 "Channel name '{}' contains wildcard characters (*, ?, [). \
368 Wildcard patterns are not supported. Please specify exact channel names.",
369 channel
370 )
371 .into());
372 }
373
374 if channel.chars().any(|c| c.is_control()) {
378 return Err(
379 format!("Channel name '{}' contains control characters", channel).into(),
380 );
381 }
382 }
383
384 if let Some(ref query) = self.event_query {
386 if query.trim().is_empty() {
387 return Err("Event query cannot be empty".into());
388 }
389
390 if query.len() > MAX_XPATH_QUERY_LENGTH {
392 return Err(format!(
393 "Event query exceeds maximum length of {} characters",
394 MAX_XPATH_QUERY_LENGTH
395 )
396 .into());
397 }
398
399 let mut bracket_count = 0i32;
401 let mut paren_count = 0i32;
402
403 for ch in query.chars() {
404 match ch {
405 '[' => bracket_count += 1,
406 ']' => bracket_count -= 1,
407 '(' => paren_count += 1,
408 ')' => paren_count -= 1,
409 _ => {}
410 }
411
412 if bracket_count < 0 || paren_count < 0 {
414 return Err("Event query contains unbalanced brackets or parentheses".into());
415 }
416 }
417
418 if bracket_count != 0 || paren_count != 0 {
420 return Err("Event query contains unbalanced brackets or parentheses".into());
421 }
422
423 let dangerous_patterns = [
426 "javascript:",
427 "vbscript:",
428 "file://", "ftp:",
430 "<script",
431 "</script",
432 ];
433
434 let query_lower = query.to_lowercase();
435 for pattern in &dangerous_patterns {
436 if query_lower.contains(pattern) {
437 return Err(format!(
438 "Event query contains potentially unsafe pattern: '{}'",
439 pattern
440 )
441 .into());
442 }
443 }
444 }
445
446 if let Some(ref event_ids) = self.only_event_ids {
448 if event_ids.is_empty() {
449 return Err("Only event IDs list cannot be empty when specified".into());
450 }
451
452 if event_ids.len() > MAX_EVENT_ID_LIST_SIZE {
453 return Err(format!(
454 "Only event IDs list cannot contain more than {} entries",
455 MAX_EVENT_ID_LIST_SIZE
456 )
457 .into());
458 }
459 }
460
461 if self.ignore_event_ids.len() > MAX_EVENT_ID_LIST_SIZE {
462 return Err(format!(
463 "Ignore event IDs list cannot contain more than {} entries",
464 MAX_EVENT_ID_LIST_SIZE
465 )
466 .into());
467 }
468
469 if let Some(ref include_fields) = self.field_filter.include_fields {
471 if include_fields.is_empty() {
472 return Err("Include fields list cannot be empty when specified".into());
473 }
474
475 if include_fields.len() > MAX_FIELD_COUNT {
476 return Err(format!(
477 "Include fields list cannot contain more than {} entries",
478 MAX_FIELD_COUNT
479 )
480 .into());
481 }
482
483 for field in include_fields {
484 if field.trim().is_empty() || field.len() > MAX_FIELD_NAME_LENGTH {
485 return Err(format!("Invalid field name: '{}'", field).into());
486 }
487
488 if field.contains('\0')
490 || field.contains('\r')
491 || field.contains('\n')
492 || field.contains('<')
493 || field.contains('>')
494 {
495 return Err(format!(
496 "Invalid field name contains dangerous characters: '{}'",
497 field
498 )
499 .into());
500 }
501 }
502 }
503
504 if let Some(ref exclude_fields) = self.field_filter.exclude_fields {
505 if exclude_fields.is_empty() {
506 return Err("Exclude fields list cannot be empty when specified".into());
507 }
508
509 if exclude_fields.len() > MAX_FIELD_COUNT {
510 return Err(format!(
511 "Exclude fields list cannot contain more than {} entries",
512 MAX_FIELD_COUNT
513 )
514 .into());
515 }
516
517 for field in exclude_fields {
518 if field.trim().is_empty() || field.len() > MAX_FIELD_NAME_LENGTH {
519 return Err(format!("Invalid field name: '{}'", field).into());
520 }
521
522 if field.contains('\0')
524 || field.contains('\r')
525 || field.contains('\n')
526 || field.contains('<')
527 || field.contains('>')
528 {
529 return Err(format!(
530 "Invalid field name contains dangerous characters: '{}'",
531 field
532 )
533 .into());
534 }
535 }
536 }
537
538 Ok(())
539 }
540}
541
542pub fn is_channel_pattern(name: &str) -> bool {
544 name.contains('*') || name.contains('?') || name.contains('[')
545}
546
547const fn default_connection_timeout_secs() -> u64 {
549 30
550}
551
552const fn default_event_timeout_ms() -> u64 {
553 5000
554}
555
556const fn default_read_existing_events() -> bool {
557 false
558}
559
560const fn default_batch_size() -> u32 {
561 100
562}
563
564const fn default_include_xml() -> bool {
565 false
566}
567
568const fn default_include_system_fields() -> bool {
569 true
570}
571
572const fn default_include_event_data() -> bool {
573 true
574}
575
576const fn default_include_user_data() -> bool {
577 true
578}
579
580const fn default_events_per_second() -> u32 {
581 0 }
583
584const fn default_max_event_data_length() -> usize {
585 0 }
587
588const fn default_checkpoint_interval_secs() -> u64 {
589 5
590}
591
592const fn default_render_message() -> bool {
593 true
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599
600 #[test]
601 fn test_default_config() {
602 let config = WindowsEventLogConfig::default();
603 assert_eq!(config.channels, vec!["System", "Application"]);
604 assert_eq!(config.connection_timeout_secs, 30);
605 assert_eq!(config.event_timeout_ms, 5000);
606 assert!(!config.read_existing_events);
607 assert_eq!(config.batch_size, 100);
608 assert!(!config.include_xml);
609 assert!(config.render_message);
610 }
611
612 #[test]
613 fn test_config_validation() {
614 let mut config = WindowsEventLogConfig::default();
615
616 assert!(config.validate().is_ok());
618
619 config.channels = vec![];
621 assert!(config.validate().is_err());
622
623 config.channels = vec!["System".to_string()];
625 assert!(config.validate().is_ok());
626
627 config.connection_timeout_secs = 0;
629 assert!(config.validate().is_err());
630
631 config.connection_timeout_secs = 30;
633 assert!(config.validate().is_ok());
634
635 config.batch_size = 0;
637 assert!(config.validate().is_err());
638
639 config.batch_size = 10;
641 assert!(config.validate().is_ok());
642
643 config.channels = vec!["".to_string()];
645 assert!(config.validate().is_err());
646
647 config.channels = vec!["System".to_string()];
649 config.event_query = Some("".to_string());
650 assert!(config.validate().is_err());
651 }
652
653 #[test]
654 fn test_field_filter_default() {
655 let filter = FieldFilter::default();
656 assert!(filter.include_system_fields);
657 assert!(filter.include_event_data);
658 assert!(filter.include_user_data);
659 assert!(filter.include_fields.is_none());
660 assert!(filter.exclude_fields.is_none());
661 }
662
663 #[test]
664 fn test_serialization() {
665 let config = WindowsEventLogConfig {
666 channels: vec!["System".to_string(), "Application".to_string()],
667 event_query: Some("*[System[Level=1]]".to_string()),
668 connection_timeout_secs: 30,
669 read_existing_events: true,
670 batch_size: 50,
671 include_xml: true,
672 event_data_format: HashMap::new(),
673 ignore_event_ids: vec![4624, 4625],
674 only_event_ids: Some(vec![1000, 1001]),
675 max_event_age_secs: Some(86400),
676 event_timeout_ms: 5000,
677 log_namespace: Some(true),
678 field_filter: FieldFilter::default(),
679 data_dir: Some(PathBuf::from("/test/data")),
680 events_per_second: 1000,
681 max_event_data_length: 0,
682 checkpoint_interval_secs: 5,
683 render_message: true,
684 acknowledgements: SourceAcknowledgementsConfig::from(true),
685 };
686
687 let serialized = serde_json::to_string(&config).expect("serialization should succeed");
689 let deserialized: WindowsEventLogConfig =
690 serde_json::from_str(&serialized).expect("deserialization should succeed");
691
692 assert_eq!(config.channels, deserialized.channels);
693 assert_eq!(config.event_query, deserialized.event_query);
694 assert_eq!(
695 config.connection_timeout_secs,
696 deserialized.connection_timeout_secs
697 );
698 assert_eq!(
699 config.read_existing_events,
700 deserialized.read_existing_events
701 );
702 assert_eq!(config.batch_size, deserialized.batch_size);
703 assert_eq!(config.render_message, deserialized.render_message);
704 }
705
706 #[test]
707 fn test_is_channel_pattern() {
708 assert!(!is_channel_pattern("System"));
710 assert!(!is_channel_pattern("Application"));
711 assert!(!is_channel_pattern("Microsoft-Windows-Sysmon/Operational"));
712
713 assert!(is_channel_pattern("Microsoft-Windows-*"));
715 assert!(is_channel_pattern("*"));
716 assert!(is_channel_pattern("Microsoft-Windows-Sysmon/*"));
717
718 assert!(is_channel_pattern("System?"));
720 assert!(is_channel_pattern("Microsoft-Windows-???"));
721
722 assert!(is_channel_pattern("Microsoft-Windows-[A-Z]*"));
724 assert!(is_channel_pattern("[Ss]ystem"));
725 }
726
727 #[test]
728 fn test_config_validation_rejects_wildcards() {
729 let mut config = WindowsEventLogConfig {
730 channels: vec!["Microsoft-Windows-*".to_string()],
731 ..Default::default()
732 };
733 let err = config.validate().unwrap_err();
734 assert!(err.to_string().contains("wildcard"));
735
736 config.channels = vec!["System?".to_string()];
738 let err = config.validate().unwrap_err();
739 assert!(err.to_string().contains("wildcard"));
740
741 config.channels = vec!["[Ss]ystem".to_string()];
743 let err = config.validate().unwrap_err();
744 assert!(err.to_string().contains("wildcard"));
745
746 config.channels = vec!["System".to_string(), "Microsoft-Windows-*".to_string()];
748 let err = config.validate().unwrap_err();
749 assert!(err.to_string().contains("wildcard"));
750
751 config.channels = vec![
753 "System".to_string(),
754 "Application".to_string(),
755 "Microsoft-Windows-Sysmon/Operational".to_string(),
756 ];
757 assert!(config.validate().is_ok());
758 }
759}