ironpost_core/
types.rs

1//! 도메인 타입 — 시스템 전역에서 사용되는 공통 타입
2//!
3//! 모든 모듈이 공유하는 데이터 구조를 정의합니다.
4//! 각 모듈은 이 타입들을 사용하여 이벤트와 데이터를 교환합니다.
5
6use std::fmt;
7use std::net::IpAddr;
8use std::time::SystemTime;
9
10use serde::{Deserialize, Serialize};
11
12/// 네트워크 패킷 정보
13///
14/// eBPF XDP 프로그램에서 캡처한 패킷의 메타데이터를 담습니다.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct PacketInfo {
17    /// 출발지 IP
18    pub src_ip: IpAddr,
19    /// 목적지 IP
20    pub dst_ip: IpAddr,
21    /// 출발지 포트
22    pub src_port: u16,
23    /// 목적지 포트
24    pub dst_port: u16,
25    /// 프로토콜 (TCP=6, UDP=17 등)
26    pub protocol: u8,
27    /// 패킷 크기 (바이트)
28    pub size: usize,
29    /// 캡처 시각
30    pub timestamp: SystemTime,
31}
32
33impl fmt::Display for PacketInfo {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(
36            f,
37            "{}:{} -> {}:{} proto={} size={}",
38            self.src_ip, self.src_port, self.dst_ip, self.dst_port, self.protocol, self.size,
39        )
40    }
41}
42
43/// 로그 엔트리
44///
45/// 파싱된 로그 레코드를 나타냅니다.
46/// 다양한 소스(syslog, 파일, journald)에서 수집된 로그를 통합 형식으로 저장합니다.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct LogEntry {
49    /// 원본 소스 (파일 경로, syslog 등)
50    pub source: String,
51    /// 타임스탬프
52    pub timestamp: SystemTime,
53    /// 호스트명
54    pub hostname: String,
55    /// 프로세스명
56    pub process: String,
57    /// 로그 메시지
58    pub message: String,
59    /// 심각도
60    pub severity: Severity,
61    /// 추가 필드 (key-value 쌍)
62    pub fields: Vec<(String, String)>,
63}
64
65impl fmt::Display for LogEntry {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        write!(
68            f,
69            "[{}] {} {}: {}",
70            self.severity, self.hostname, self.process, self.message,
71        )
72    }
73}
74
75/// 보안 알림
76///
77/// 탐지 규칙에 매칭되어 생성된 보안 알림을 나타냅니다.
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Alert {
80    /// 알림 ID
81    pub id: String,
82    /// 알림 제목
83    pub title: String,
84    /// 상세 설명
85    pub description: String,
86    /// 심각도
87    pub severity: Severity,
88    /// 탐지 규칙명
89    pub rule_name: String,
90    /// 관련 소스 IP (있을 경우)
91    pub source_ip: Option<IpAddr>,
92    /// 관련 대상 IP (있을 경우)
93    pub target_ip: Option<IpAddr>,
94    /// 생성 시각
95    pub created_at: SystemTime,
96}
97
98impl fmt::Display for Alert {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(
101            f,
102            "[{}] {} (rule: {})",
103            self.severity, self.title, self.rule_name,
104        )
105    }
106}
107
108/// 심각도 레벨
109///
110/// 보안 이벤트의 심각도를 나타냅니다.
111/// `Ord` 구현으로 심각도 비교가 가능합니다 (`Info < Low < Medium < High < Critical`).
112#[derive(
113    Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
114)]
115pub enum Severity {
116    /// 정보성 이벤트
117    #[default]
118    Info,
119    /// 낮은 심각도
120    Low,
121    /// 중간 심각도
122    Medium,
123    /// 높은 심각도
124    High,
125    /// 치명적 — 즉시 대응 필요
126    Critical,
127}
128
129impl Severity {
130    /// 문자열에서 심각도를 파싱합니다.
131    ///
132    /// 대소문자를 구분하지 않습니다.
133    pub fn from_str_loose(s: &str) -> Option<Self> {
134        match s.to_lowercase().as_str() {
135            "info" | "informational" => Some(Self::Info),
136            "low" => Some(Self::Low),
137            "medium" | "med" => Some(Self::Medium),
138            "high" => Some(Self::High),
139            "critical" | "crit" => Some(Self::Critical),
140            _ => None,
141        }
142    }
143}
144
145impl fmt::Display for Severity {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        match self {
148            Self::Info => write!(f, "Info"),
149            Self::Low => write!(f, "Low"),
150            Self::Medium => write!(f, "Medium"),
151            Self::High => write!(f, "High"),
152            Self::Critical => write!(f, "Critical"),
153        }
154    }
155}
156
157/// 컨테이너 정보
158///
159/// 모니터링 대상 컨테이너의 메타데이터를 나타냅니다.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ContainerInfo {
162    /// 컨테이너 ID
163    pub id: String,
164    /// 컨테이너 이름
165    pub name: String,
166    /// 이미지명
167    pub image: String,
168    /// 상태 (running, stopped 등)
169    pub status: String,
170    /// 생성 시각
171    pub created_at: SystemTime,
172}
173
174impl fmt::Display for ContainerInfo {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(
177            f,
178            "{} ({}) image={} status={}",
179            self.name,
180            &self.id[..12.min(self.id.len())],
181            self.image,
182            self.status,
183        )
184    }
185}
186
187/// SBOM 취약점 정보
188///
189/// 취약점 데이터베이스에서 매칭된 CVE 정보를 나타냅니다.
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct Vulnerability {
192    /// CVE ID (예: CVE-2024-1234)
193    pub cve_id: String,
194    /// 영향받는 패키지명
195    pub package: String,
196    /// 영향받는 버전
197    pub affected_version: String,
198    /// 수정된 버전 (있을 경우)
199    pub fixed_version: Option<String>,
200    /// 심각도
201    pub severity: Severity,
202    /// 취약점 설명
203    pub description: String,
204}
205
206impl fmt::Display for Vulnerability {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        write!(
209            f,
210            "{} [{}] {} {} (fixed: {})",
211            self.cve_id,
212            self.severity,
213            self.package,
214            self.affected_version,
215            self.fixed_version.as_deref().unwrap_or("N/A"),
216        )
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn severity_ordering() {
226        assert!(Severity::Info < Severity::Low);
227        assert!(Severity::Low < Severity::Medium);
228        assert!(Severity::Medium < Severity::High);
229        assert!(Severity::High < Severity::Critical);
230    }
231
232    #[test]
233    fn severity_default_is_info() {
234        assert_eq!(Severity::default(), Severity::Info);
235    }
236
237    #[test]
238    fn severity_display() {
239        assert_eq!(Severity::Info.to_string(), "Info");
240        assert_eq!(Severity::Low.to_string(), "Low");
241        assert_eq!(Severity::Medium.to_string(), "Medium");
242        assert_eq!(Severity::High.to_string(), "High");
243        assert_eq!(Severity::Critical.to_string(), "Critical");
244    }
245
246    #[test]
247    fn severity_from_str_loose() {
248        assert_eq!(Severity::from_str_loose("info"), Some(Severity::Info));
249        assert_eq!(
250            Severity::from_str_loose("CRITICAL"),
251            Some(Severity::Critical)
252        );
253        assert_eq!(Severity::from_str_loose("Med"), Some(Severity::Medium));
254        assert_eq!(
255            Severity::from_str_loose("informational"),
256            Some(Severity::Info)
257        );
258        assert_eq!(Severity::from_str_loose("crit"), Some(Severity::Critical));
259        assert_eq!(Severity::from_str_loose("unknown"), None);
260    }
261
262    #[test]
263    fn severity_serialize_deserialize() {
264        let severity = Severity::High;
265        let json = serde_json::to_string(&severity).unwrap();
266        let deserialized: Severity = serde_json::from_str(&json).unwrap();
267        assert_eq!(severity, deserialized);
268    }
269
270    #[test]
271    fn packet_info_display() {
272        let info = PacketInfo {
273            src_ip: "192.168.1.1".parse().unwrap(),
274            dst_ip: "10.0.0.1".parse().unwrap(),
275            src_port: 12345,
276            dst_port: 80,
277            protocol: 6,
278            size: 1500,
279            timestamp: SystemTime::now(),
280        };
281        let display = info.to_string();
282        assert!(display.contains("192.168.1.1:12345"));
283        assert!(display.contains("10.0.0.1:80"));
284    }
285
286    #[test]
287    fn log_entry_display() {
288        let entry = LogEntry {
289            source: "syslog".to_owned(),
290            timestamp: SystemTime::now(),
291            hostname: "server-01".to_owned(),
292            process: "sshd".to_owned(),
293            message: "session opened".to_owned(),
294            severity: Severity::Info,
295            fields: vec![],
296        };
297        let display = entry.to_string();
298        assert!(display.contains("Info"));
299        assert!(display.contains("server-01"));
300        assert!(display.contains("sshd"));
301    }
302
303    #[test]
304    fn alert_display() {
305        let alert = Alert {
306            id: "alert-001".to_owned(),
307            title: "Brute force".to_owned(),
308            description: "desc".to_owned(),
309            severity: Severity::High,
310            rule_name: "ssh_brute".to_owned(),
311            source_ip: None,
312            target_ip: None,
313            created_at: SystemTime::now(),
314        };
315        let display = alert.to_string();
316        assert!(display.contains("High"));
317        assert!(display.contains("Brute force"));
318        assert!(display.contains("ssh_brute"));
319    }
320
321    #[test]
322    fn container_info_display() {
323        let info = ContainerInfo {
324            id: "abc123def456".to_owned(),
325            name: "web-server".to_owned(),
326            image: "nginx:latest".to_owned(),
327            status: "running".to_owned(),
328            created_at: SystemTime::now(),
329        };
330        let display = info.to_string();
331        assert!(display.contains("web-server"));
332        assert!(display.contains("nginx:latest"));
333    }
334
335    #[test]
336    fn vulnerability_display() {
337        let vuln = Vulnerability {
338            cve_id: "CVE-2024-1234".to_owned(),
339            package: "openssl".to_owned(),
340            affected_version: "1.1.1".to_owned(),
341            fixed_version: Some("1.1.1t".to_owned()),
342            severity: Severity::Critical,
343            description: "Buffer overflow".to_owned(),
344        };
345        let display = vuln.to_string();
346        assert!(display.contains("CVE-2024-1234"));
347        assert!(display.contains("Critical"));
348        assert!(display.contains("1.1.1t"));
349    }
350
351    #[test]
352    fn vulnerability_display_no_fix() {
353        let vuln = Vulnerability {
354            cve_id: "CVE-2024-5678".to_owned(),
355            package: "libxml2".to_owned(),
356            affected_version: "2.9.0".to_owned(),
357            fixed_version: None,
358            severity: Severity::Medium,
359            description: "XXE vulnerability".to_owned(),
360        };
361        assert!(vuln.to_string().contains("N/A"));
362    }
363
364    #[test]
365    fn packet_info_serialize_roundtrip() {
366        let info = PacketInfo {
367            src_ip: "::1".parse().unwrap(),
368            dst_ip: "::1".parse().unwrap(),
369            src_port: 443,
370            dst_port: 54321,
371            protocol: 6,
372            size: 64,
373            timestamp: SystemTime::now(),
374        };
375        let json = serde_json::to_string(&info).unwrap();
376        let deserialized: PacketInfo = serde_json::from_str(&json).unwrap();
377        assert_eq!(info.src_ip, deserialized.src_ip);
378        assert_eq!(info.dst_port, deserialized.dst_port);
379    }
380}