55from pathlib import Path
66from typing import List , Dict , Set , Tuple , Optional
77from functools import lru_cache
8+ from collections import defaultdict # Import defaultdict
89
910# --- Configuration ---
1011LOG_LEVEL = logging .INFO # Adjust as needed (DEBUG, INFO, WARNING, ERROR)
2122
2223# Unsupported ModSecurity directives (expand as needed)
2324UNSUPPORTED_PATTERNS = [
24- "@pmFromFile" , # File lookups not directly supported
25+ "@pmFromFile" , # File lookups not directly supported
2526 # You might handle some of these with ctl:ruleRemoveTargetById later
2627]
2728# Supported ModSecurity operators and their rough translations (for logging/info)
2829SUPPORTED_OPERATORS = {
29- "@rx" : "Regular Expression" ,
30- "@streq" : "String Equals" ,
31- "@contains" : "Contains String" ,
32- "@beginsWith" : "Begins With" ,
33- "@endsWith" : "Ends With" ,
34- "@within" : "Contained Within" ,
35- "@ipMatch" : "IP Address Match" ,
36- # ... add more as needed
30+ "@rx" : "Regular Expression" ,
31+ "@streq" : "String Equals" ,
32+ "@contains" : "Contains String" ,
33+ "@beginsWith" : "Begins With" ,
34+ "@endsWith" : "Ends With" ,
35+ "@within" : "Contained Within" ,
36+ "@ipMatch" : "IP Address Match" ,
37+ # ... add more as needed
3738}
3839
3940# --- Logging Setup ---
@@ -78,7 +79,7 @@ def _determine_variables(location: str) -> str:
7879 # Add other location mappings as needed
7980 else :
8081 logger .warning (f"Unknown location '{ location } ', defaulting to REQUEST_URI" )
81- return "REQUEST_URI" # Default variable
82+ return "REQUEST_URI" # Default variable
8283
8384
8485def generate_apache_waf (rules : List [Dict ]) -> None :
@@ -93,16 +94,24 @@ def generate_apache_waf(rules: List[Dict]) -> None:
9394
9495 for rule in rules :
9596 rule_id = rule .get ("id" , "no_id" ) # Get rule ID
96- if not isinstance (rule_id , int ): # check if is an int
97- # Extract ID from rule and convert to an integer
97+ if not isinstance (rule_id , int ): # check if is an int
98+ # Extract ID from rule and convert to an integer
9899 match = re .search (r'id:(\d+)' , rule_id )
99- rule_id = int (match .group (1 )) if match else rule_id_counter
100- rule_id_counter += 1
100+ if match :
101+ try :
102+ rule_id = int (match .group (1 ))
103+ except ValueError :
104+ logger .warning (f"Invalid rule ID '{ match .group (1 )} ' in rule: { rule } . Using generated ID." )
105+ rule_id = rule_id_counter
106+ rule_id_counter += 1
107+ else :
108+ rule_id = rule_id_counter
109+ rule_id_counter += 1
101110
102111 category = rule .get ("category" , "generic" ).lower ()
103112 pattern = rule ["pattern" ]
104- location = rule .get ("location" , "REQUEST_URI" ) # Set a default variable
105- severity = rule .get ("severity" , "CRITICAL" ).upper () # CRITICAL, ERROR, WARNING, NOTICE
113+ location = rule .get ("location" , "REQUEST_URI" ) # Set a default variable
114+ severity = rule .get ("severity" , "CRITICAL" ).upper () # CRITICAL, ERROR, WARNING, NOTICE
106115 # --- Operator Handling ---
107116 operator_used = "Unknown" # Default
108117 for op in SUPPORTED_OPERATORS :
@@ -133,7 +142,7 @@ def generate_apache_waf(rules: List[Dict]) -> None:
133142 phase = 2 , # Phase 2 (request body processing) is common, adjust if needed
134143 actions = DEFAULT_ACTIONS ,
135144 )
136- categorized_rules [category ].add (rule_str ) # added into a dict
145+ categorized_rules [category ].add (rule_str ) # added into a dict
137146
138147
139148 # --- File Output ---
0 commit comments