sentinel_modsec/engine/
mod.rs

1//! Transaction engine for ModSecurity rule processing.
2
3pub mod chain;
4pub mod intervention;
5pub mod phase;
6pub mod ruleset;
7pub mod scoring;
8pub mod transaction;
9
10pub use intervention::Intervention;
11pub use ruleset::{CompiledRuleset, Rules};
12pub use transaction::Transaction;
13
14use crate::error::Result;
15use std::sync::Arc;
16
17/// Main ModSecurity engine.
18pub struct ModSecurity {
19    /// Compiled ruleset.
20    ruleset: Arc<CompiledRuleset>,
21    /// Default block status code.
22    default_status: u16,
23}
24
25impl ModSecurity {
26    /// Create a new ModSecurity instance with the given ruleset.
27    pub fn new(ruleset: CompiledRuleset) -> Self {
28        Self {
29            ruleset: Arc::new(ruleset),
30            default_status: 403,
31        }
32    }
33
34    /// Load rules from a file.
35    pub fn from_file(path: &str) -> Result<Self> {
36        let ruleset = CompiledRuleset::from_file(path)?;
37        Ok(Self::new(ruleset))
38    }
39
40    /// Load rules from a string.
41    pub fn from_string(rules: &str) -> Result<Self> {
42        let ruleset = CompiledRuleset::from_string(rules)?;
43        Ok(Self::new(ruleset))
44    }
45
46    /// Set the default block status code.
47    pub fn set_default_status(&mut self, status: u16) {
48        self.default_status = status;
49    }
50
51    /// Create a new transaction for processing a request.
52    pub fn new_transaction(&self) -> Transaction {
53        Transaction::new(Arc::clone(&self.ruleset), self.default_status)
54    }
55
56    /// Get the ruleset.
57    pub fn ruleset(&self) -> &CompiledRuleset {
58        &self.ruleset
59    }
60
61    /// Get the number of rules.
62    pub fn rule_count(&self) -> usize {
63        self.ruleset.rule_count()
64    }
65}
66
67impl std::fmt::Debug for ModSecurity {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.debug_struct("ModSecurity")
70            .field("rule_count", &self.ruleset.rule_count())
71            .field("default_status", &self.default_status)
72            .finish()
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_modsec_from_string() {
82        let rules = r#"
83            SecRule REQUEST_URI "@contains /admin" "id:1,phase:1,deny"
84        "#;
85        let modsec = ModSecurity::from_string(rules).unwrap();
86        assert_eq!(modsec.rule_count(), 1);
87    }
88
89    #[test]
90    fn test_new_transaction() {
91        let rules = r#"
92            SecRule REQUEST_URI "@contains /admin" "id:1,phase:1,deny"
93        "#;
94        let modsec = ModSecurity::from_string(rules).unwrap();
95        let _tx = modsec.new_transaction();
96    }
97}