Skip to main content

zentinel_modsec/parser/
directive.rs

1//! Directive types for ModSecurity configuration.
2
3use super::{Action, OperatorSpec, VariableSpec};
4use crate::error::SourceLocation;
5use std::path::PathBuf;
6
7/// A parsed ModSecurity directive.
8#[derive(Debug, Clone)]
9pub enum Directive {
10    /// SecRule directive - the main rule type.
11    SecRule(SecRule),
12    /// SecAction directive - actions without matching.
13    SecAction(SecAction),
14    /// SecMarker directive - named marker for skipAfter.
15    SecMarker(SecMarker),
16    /// SecRuleEngine directive - enable/disable rules.
17    SecRuleEngine(RuleEngineMode),
18    /// SecDefaultAction directive - default actions for rules.
19    SecDefaultAction(Vec<Action>),
20    /// SecRuleRemoveById directive - remove rules by ID or ID range.
21    SecRuleRemoveById(Vec<RuleIdSelector>),
22    /// SecRuleUpdateTargetById directive - update rule targets (CRS exclusions).
23    SecRuleUpdateTargetById(UpdateTargetById),
24    /// SecRuleUpdateActionById directive - update rule actions.
25    SecRuleUpdateActionById { id: u64, actions: Vec<Action> },
26    /// SecRequestBodyAccess directive.
27    SecRequestBodyAccess(bool),
28    /// SecResponseBodyAccess directive.
29    SecResponseBodyAccess(bool),
30    /// SecRequestBodyLimit directive.
31    SecRequestBodyLimit(usize),
32    /// SecResponseBodyLimit directive.
33    SecResponseBodyLimit(usize),
34    /// Include directive - include another file.
35    Include(PathBuf),
36    /// Unknown directive (logged and skipped).
37    Unknown(String),
38}
39
40/// A rule ID selector: a single ID or an inclusive ID range.
41///
42/// ModSecurity accepts both forms in `SecRuleRemoveById` and
43/// `SecRuleUpdateTargetById` (e.g. `942100` or `942100-942199`).
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum RuleIdSelector {
46    /// A single rule ID.
47    Single(u64),
48    /// An inclusive range of rule IDs.
49    Range(u64, u64),
50}
51
52impl RuleIdSelector {
53    /// Check whether a rule ID matches this selector.
54    pub fn matches(&self, id: u64) -> bool {
55        match *self {
56            Self::Single(s) => id == s,
57            Self::Range(start, end) => (start..=end).contains(&id),
58        }
59    }
60}
61
62/// A parsed `SecRuleUpdateTargetById` directive.
63///
64/// Per ModSecurity semantics:
65/// - positive targets (`ARGS:foo`) are appended to the rule's variable list
66///   (or replace `replaced` when a third argument is given);
67/// - `!`-prefixed targets (`!ARGS:password`) add a target exclusion so that
68///   the named variable is no longer inspected by the rule.
69#[derive(Debug, Clone)]
70pub struct UpdateTargetById {
71    /// The rule IDs (or ranges) to update.
72    pub ids: Vec<RuleIdSelector>,
73    /// Positive targets to append (or to substitute for `replaced`).
74    pub additions: Vec<VariableSpec>,
75    /// Target exclusions (the text after `!`, e.g. `ARGS:password`).
76    pub exclusions: Vec<String>,
77    /// Optional target to replace (third directive argument).
78    pub replaced: Option<String>,
79    /// Source location for diagnostics.
80    pub location: SourceLocation,
81}
82
83/// A SecRule directive.
84#[derive(Debug, Clone)]
85pub struct SecRule {
86    /// Variables to inspect.
87    pub variables: Vec<VariableSpec>,
88    /// Operator to apply.
89    pub operator: OperatorSpec,
90    /// Actions to execute on match.
91    pub actions: Vec<Action>,
92    /// Source location for error reporting.
93    pub location: SourceLocation,
94}
95
96/// A SecAction directive.
97#[derive(Debug, Clone)]
98pub struct SecAction {
99    /// Actions to execute.
100    pub actions: Vec<Action>,
101    /// Source location for error reporting.
102    pub location: SourceLocation,
103}
104
105/// A SecMarker directive.
106#[derive(Debug, Clone)]
107pub struct SecMarker {
108    /// Marker name.
109    pub name: String,
110}
111
112/// Rule engine mode.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum RuleEngineMode {
115    /// Rules are enabled and will block.
116    On,
117    /// Rules are disabled.
118    Off,
119    /// Rules are enabled but will only log, not block.
120    DetectionOnly,
121}
122
123impl Default for RuleEngineMode {
124    fn default() -> Self {
125        Self::Off
126    }
127}
128
129impl SecRule {
130    /// Check if this rule has the chain action.
131    pub fn is_chained(&self) -> bool {
132        self.actions.iter().any(|a| matches!(a, Action::Flow(super::FlowAction::Chain)))
133    }
134
135    /// Get the rule ID if present.
136    pub fn id(&self) -> Option<u64> {
137        for action in &self.actions {
138            if let Action::Metadata(super::MetadataAction::Id(id)) = action {
139                return Some(*id);
140            }
141        }
142        None
143    }
144
145    /// Get the phase for this rule (defaults to 2).
146    pub fn phase(&self) -> u8 {
147        for action in &self.actions {
148            if let Action::Metadata(super::MetadataAction::Phase(phase)) = action {
149                return *phase;
150            }
151        }
152        2 // Default phase is 2 (request body)
153    }
154}