sentinel_modsec/actions/
metadata.rs

1//! Metadata actions (id, msg, severity, tag, etc.).
2
3use super::RuleMetadata;
4
5/// Severity levels as defined in ModSecurity.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
7#[repr(u8)]
8pub enum Severity {
9    Emergency = 0,
10    Alert = 1,
11    Critical = 2,
12    Error = 3,
13    Warning = 4,
14    Notice = 5,
15    Info = 6,
16    Debug = 7,
17}
18
19impl From<u8> for Severity {
20    fn from(value: u8) -> Self {
21        match value {
22            0 => Severity::Emergency,
23            1 => Severity::Alert,
24            2 => Severity::Critical,
25            3 => Severity::Error,
26            4 => Severity::Warning,
27            5 => Severity::Notice,
28            6 => Severity::Info,
29            _ => Severity::Debug,
30        }
31    }
32}
33
34impl Severity {
35    /// Get severity name.
36    pub fn name(&self) -> &'static str {
37        match self {
38            Severity::Emergency => "EMERGENCY",
39            Severity::Alert => "ALERT",
40            Severity::Critical => "CRITICAL",
41            Severity::Error => "ERROR",
42            Severity::Warning => "WARNING",
43            Severity::Notice => "NOTICE",
44            Severity::Info => "INFO",
45            Severity::Debug => "DEBUG",
46        }
47    }
48}
49
50impl RuleMetadata {
51    /// Get severity as enum.
52    pub fn severity_level(&self) -> Option<Severity> {
53        self.severity.map(Severity::from)
54    }
55
56    /// Format as log message.
57    pub fn format_log(&self) -> String {
58        let mut parts = Vec::new();
59
60        if let Some(ref id) = self.id {
61            parts.push(format!("[id \"{}\"]", id));
62        }
63
64        if let Some(ref msg) = self.msg {
65            parts.push(format!("[msg \"{}\"]", msg));
66        }
67
68        if let Some(sev) = self.severity {
69            parts.push(format!("[severity \"{}\"]", Severity::from(sev).name()));
70        }
71
72        for tag in &self.tags {
73            parts.push(format!("[tag \"{}\"]", tag));
74        }
75
76        if let Some(ref rev) = self.rev {
77            parts.push(format!("[rev \"{}\"]", rev));
78        }
79
80        if let Some(ref ver) = self.ver {
81            parts.push(format!("[ver \"{}\"]", ver));
82        }
83
84        parts.join(" ")
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_severity_from_u8() {
94        assert_eq!(Severity::from(0), Severity::Emergency);
95        assert_eq!(Severity::from(2), Severity::Critical);
96        assert_eq!(Severity::from(4), Severity::Warning);
97        assert_eq!(Severity::from(99), Severity::Debug);
98    }
99
100    #[test]
101    fn test_format_log() {
102        let meta = RuleMetadata {
103            id: Some("942100".to_string()),
104            msg: Some("SQL Injection Attack".to_string()),
105            severity: Some(2),
106            tags: vec!["attack-sqli".to_string(), "OWASP_CRS".to_string()],
107            ..Default::default()
108        };
109
110        let log = meta.format_log();
111        assert!(log.contains("[id \"942100\"]"));
112        assert!(log.contains("[msg \"SQL Injection Attack\"]"));
113        assert!(log.contains("[severity \"CRITICAL\"]"));
114        assert!(log.contains("[tag \"attack-sqli\"]"));
115    }
116}