1use crate::entry::{LogEntry, SecurityLevel};
4
5pub struct CEFFormatter;
7
8impl CEFFormatter {
9 pub fn format(entry: &LogEntry) -> String {
12 let device_vendor = "GuardsArm";
13 let device_product = "SecureLogger";
14 let device_version = "1.0";
15 let event_class_id = Self::get_event_class_id(&entry.level);
16 let name = &entry.message;
17 let severity = entry.level.severity();
18
19 let mut extensions = Vec::new();
21 extensions.push(format!("rt={}", entry.timestamp.timestamp_millis()));
22
23 if let Some(ref source) = entry.source {
24 extensions.push(format!("shost={}", source));
25 }
26
27 if let Some(ref category) = entry.category {
28 extensions.push(format!("cat={}", category));
29 }
30
31 if let Some(ref meta) = entry.metadata {
32 extensions.push(format!("cs1={}", meta));
33 extensions.push("cs1Label=metadata".to_string());
34 }
35
36 extensions.push(format!("msg={}", entry.message));
37
38 format!(
39 "CEF:0|{}|{}|{}|{}|{}|{}|{}",
40 device_vendor,
41 device_product,
42 device_version,
43 event_class_id,
44 name,
45 severity,
46 extensions.join(" ")
47 )
48 }
49
50 fn get_event_class_id(level: &SecurityLevel) -> &'static str {
51 match level {
52 SecurityLevel::Info => "INFO-001",
53 SecurityLevel::Warning => "WARN-002",
54 SecurityLevel::SecurityEvent => "SEC-003",
55 SecurityLevel::Critical => "CRIT-004",
56 SecurityLevel::Audit => "AUDIT-005",
57 }
58 }
59}
60
61pub struct LEEFFormatter;
63
64impl LEEFFormatter {
65 pub fn format(entry: &LogEntry) -> String {
68 let vendor = "GuardsArm";
69 let product = "SecureLogger";
70 let version = "1.0";
71 let event_id = Self::get_event_id(&entry.level);
72 let delimiter = "\t";
73
74 let mut fields = Vec::new();
75 fields.push(format!("devTime={}", entry.timestamp.to_rfc3339()));
76 fields.push(format!("severity={}", entry.level.severity()));
77 fields.push(format!("cat={}", entry.level.as_str()));
78 fields.push(format!("msg={}", entry.message));
79
80 if let Some(ref source) = entry.source {
81 fields.push(format!("src={}", source));
82 }
83
84 if let Some(ref category) = entry.category {
85 fields.push(format!("eventCategory={}", category));
86 }
87
88 if let Some(ref meta) = entry.metadata {
89 fields.push(format!("usrName={}", meta));
90 }
91
92 format!(
93 "LEEF:2.0|{}|{}|{}|{}|{}|{}",
94 vendor,
95 product,
96 version,
97 event_id,
98 delimiter,
99 fields.join(delimiter)
100 )
101 }
102
103 fn get_event_id(level: &SecurityLevel) -> &'static str {
104 match level {
105 SecurityLevel::Info => "1000",
106 SecurityLevel::Warning => "2000",
107 SecurityLevel::SecurityEvent => "3000",
108 SecurityLevel::Critical => "4000",
109 SecurityLevel::Audit => "5000",
110 }
111 }
112}
113
114pub struct SyslogFormatter;
116
117impl SyslogFormatter {
118 pub fn format(entry: &LogEntry) -> String {
120 let priority = Self::calculate_priority(&entry.level);
121 let version = 1;
122 let timestamp = entry.timestamp.to_rfc3339();
123 let hostname = entry.source.as_deref().unwrap_or("-");
124 let app_name = "SecureLogger";
125 let proc_id = std::process::id();
126 let msg_id = entry.level.as_str();
127
128 let structured_data = if let Some(ref meta) = entry.metadata {
130 format!(
131 "[metadata@32473 data=\"{}\"]",
132 meta.to_string().replace('"', "\\\"")
133 )
134 } else {
135 "-".to_string()
136 };
137
138 format!(
139 "<{}>{} {} {} {} {} {} {} {}",
140 priority,
141 version,
142 timestamp,
143 hostname,
144 app_name,
145 proc_id,
146 msg_id,
147 structured_data,
148 entry.message
149 )
150 }
151
152 fn calculate_priority(level: &SecurityLevel) -> u8 {
153 let severity = match level {
156 SecurityLevel::Info => 6, SecurityLevel::Warning => 4, SecurityLevel::SecurityEvent => 2, SecurityLevel::Critical => 1, SecurityLevel::Audit => 5, };
162 (16 * 8) + severity
163 }
164}
165
166pub struct SplunkFormatter;
168
169impl SplunkFormatter {
170 pub fn format(entry: &LogEntry) -> String {
172 let event = serde_json::json!({
173 "time": entry.timestamp.timestamp(),
174 "host": entry.source.as_ref().unwrap_or(&"unknown".to_string()),
175 "source": "secure_logger",
176 "sourcetype": "_json",
177 "event": {
178 "level": entry.level.as_str(),
179 "message": entry.message,
180 "severity": entry.level.severity(),
181 "category": entry.category,
182 "metadata": entry.metadata,
183 "integrity_hash": entry.integrity_hash,
184 }
185 });
186
187 event.to_string()
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 fn create_test_entry() -> LogEntry {
196 LogEntry::new_with_context(
197 SecurityLevel::SecurityEvent,
198 "Failed login attempt".to_string(),
199 Some(serde_json::json!({"username": "admin", "ip": "192.168.1.100"})),
200 Some("web-server-01".to_string()),
201 Some("authentication".to_string()),
202 )
203 }
204
205 #[test]
206 fn test_cef_format() {
207 let entry = create_test_entry();
208 let cef = CEFFormatter::format(&entry);
209
210 assert!(cef.starts_with("CEF:0|"));
211 assert!(cef.contains("GuardsArm"));
212 assert!(cef.contains("SecureLogger"));
213 assert!(cef.contains("SEC-003"));
214 assert!(cef.contains("Failed login attempt"));
215 }
216
217 #[test]
218 fn test_leef_format() {
219 let entry = create_test_entry();
220 let leef = LEEFFormatter::format(&entry);
221
222 assert!(leef.starts_with("LEEF:2.0|"));
223 assert!(leef.contains("GuardsArm"));
224 assert!(leef.contains("SecureLogger"));
225 assert!(leef.contains("3000"));
226 assert!(leef.contains("Failed login attempt"));
227 }
228
229 #[test]
230 fn test_syslog_format() {
231 let entry = create_test_entry();
232 let syslog = SyslogFormatter::format(&entry);
233
234 assert!(syslog.starts_with("<130>1")); assert!(syslog.contains("SecureLogger"));
236 assert!(syslog.contains("Failed login attempt"));
237 }
238
239 #[test]
240 fn test_splunk_format() {
241 let entry = create_test_entry();
242 let splunk = SplunkFormatter::format(&entry);
243
244 assert!(splunk.contains("\"source\":\"secure_logger\""));
245 assert!(splunk.contains("\"level\":\"SECURITY_EVENT\""));
246 assert!(splunk.contains("Failed login attempt"));
247 }
248
249 #[test]
250 fn test_cef_severity_levels() {
251 let levels = vec![
252 (SecurityLevel::Info, "1"),
253 (SecurityLevel::Warning, "3"),
254 (SecurityLevel::SecurityEvent, "5"),
255 (SecurityLevel::Critical, "8"),
256 ];
257
258 for (level, expected_sev) in levels {
259 let entry = LogEntry::new(level, "Test".to_string(), None);
260 let cef = CEFFormatter::format(&entry);
261 assert!(cef.contains(&format!("|{}|", expected_sev)));
262 }
263 }
264
265 #[test]
266 fn test_leef_event_ids() {
267 let levels = vec![
268 (SecurityLevel::Info, "1000"),
269 (SecurityLevel::Warning, "2000"),
270 (SecurityLevel::SecurityEvent, "3000"),
271 (SecurityLevel::Critical, "4000"),
272 (SecurityLevel::Audit, "5000"),
273 ];
274
275 for (level, expected_id) in levels {
276 let entry = LogEntry::new(level, "Test".to_string(), None);
277 let leef = LEEFFormatter::format(&entry);
278 assert!(leef.contains(expected_id));
279 }
280 }
281}