GUI editor to tame mod_security rules

⌈⌋ branch:  modseccfg


Check-in [acaec56692]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Move to safer Unicode glyphs. Tk doesn't like current system setup: <blockquote> X Error of failed request: BadLength (poly request too large or internal Xlib length error) Major opcode of failed request: 139 (RENDER) Minor opcode of failed request: 20 (RenderAddGlyphs) Serial number of failed request: 28016 Current serial number in output stream: 28018 </blockquote>
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: acaec566926a15fda95c86346f98aedf61168e9e8974c415759f894e727f1249
User & Date: mario 2021-02-02 16:07:01
Context
2021-02-24
20:16
Remove remaining emoji Unicode occurences (info, modify, vhosts) check-in: 5f05a5d785 user: mario tags: trunk
2021-02-02
16:07
Move to safer Unicode glyphs. Tk doesn't like current system setup: <blockquote> X Error of failed request: BadLength (poly request too large or internal Xlib length error) Major opcode of failed request: 139 (RENDER) Minor opcode of failed request: 20 (RenderAddGlyphs) Serial number of failed request: 28016 Current serial number in output stream: 28018 </blockquote> check-in: acaec56692 user: mario tags: trunk
12:32
Use dateutil.parser fuzzy=True check-in: 81e5866c7a user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to modseccfg/mainwindow.py.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
    # make tk widget methods more accessible
    @inject(sg.Element, sg.Window)
    def __getattr__(self, name):
        if "Widget" in self.__dict__ and hasattr(self.Widget, name):
            return getattr(self.Widget, name)
        elif "TKroot" in self.__dict__ and hasattr(self.TKroot, name):
            return getattr(self.TKroot, name)
        elif name in self.dict:
            return self.__dict__.get(name)

    @staticmethod
    def rules(log_count={}, rulestate={}):
        rule_tree = sg.TreeData()
        hidden = [0]
        for id,r in vhosts.rules.items():
................................................................................
                continue
            parent = ""
            if r.chained_to:
                parent = r.chained_to
                if parent in hidden:
                    continue
            # prepare treedata attributes
            state = rulestate.get(id, "")  # formerly: -1=➗, 0=❌, 1=, undef=✅
            rule_tree.insert(
                parent=parent,
                key=id,
                text=id,
                values=[
                   state, str(id), r.msg, r.tag_primary, log_count.get(id, 0)
                ],
................................................................................
layout = [
    [
        sg.Column([
            # menu
            [sg.Menu(menu, key="menu")],
            # button row
            [
                sg.Button(" Info", tooltip="SecRule details"),
                sg.Button(" Disable", tooltip="SecRuleRemoveById"),
                sg.Button(" Enable", tooltip="remove SecRuleRemove"),
                sg.Button(" Modify", tooltip="SecRuleUpdateAction/Target", disabled=0),
                sg.ButtonMenu("❮❯ Wrap", ["Wrap",["<FilesMatch>","<Location>","<Directory>"]], disabled=0, k="menu_wrap"),
                sg.T(" " * 18),
                sg.Button("Filter", key="filter_log", button_color=("white","gray"), font="Sans 10", tooltip="Apply filter phrase to current log"),
                sg.Combo(values=["", "injection", "500|429", "bot"], size=(20,1), key="log_filter", enable_events=True, tooltip="Regex to filter with")
            ],
            [sg.T("  "*63+"↓")],
            # comboboxes
            [sg.Text("vhost/conf", font="bold"),
             sg.Combo(key="confn", size=(50,1), values=vhosts.list_vhosts(), enable_events=True, tooltip="Which *.conf to edit"),
             sg.Text("Log"),
             sg.Combo(key="logfn", values=logs.find_logs(), size=(30,1), enable_events=True, tooltip="Error/Audit log to show"),
             ],
        ]),







|







 







|







 







|
|
|
|
|




|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
..
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
    # make tk widget methods more accessible
    @inject(sg.Element, sg.Window)
    def __getattr__(self, name):
        if "Widget" in self.__dict__ and hasattr(self.Widget, name):
            return getattr(self.Widget, name)
        elif "TKroot" in self.__dict__ and hasattr(self.TKroot, name):
            return getattr(self.TKroot, name)
        elif name in self.__dict__:
            return self.__dict__.get(name)

    @staticmethod
    def rules(log_count={}, rulestate={}):
        rule_tree = sg.TreeData()
        hidden = [0]
        for id,r in vhosts.rules.items():
................................................................................
                continue
            parent = ""
            if r.chained_to:
                parent = r.chained_to
                if parent in hidden:
                    continue
            # prepare treedata attributes
            state = rulestate.get(id, "🗸")  # formerly: -1=➗, 0=❌, 1=, undef=✅
            rule_tree.insert(
                parent=parent,
                key=id,
                text=id,
                values=[
                   state, str(id), r.msg, r.tag_primary, log_count.get(id, 0)
                ],
................................................................................
layout = [
    [
        sg.Column([
            # menu
            [sg.Menu(menu, key="menu")],
            # button row
            [
                sg.Button("🛈 Info", tooltip="SecRule details"),#⭐
                sg.Button("🗶 Disable", tooltip="SecRuleRemoveById"),#❌
                sg.Button("🗸 Enable", tooltip="remove SecRuleRemove"),#✅
                sg.Button(" Modify", tooltip="SecRuleUpdateAction/Target", disabled=0),#➗
                sg.ButtonMenu(" Wrap", ["Wrap",["<FilesMatch>","<Location>","<Directory>"]], disabled=0, k="menu_wrap"),#❮❯
                sg.T(" " * 18),
                sg.Button("Filter", key="filter_log", button_color=("white","gray"), font="Sans 10", tooltip="Apply filter phrase to current log"),
                sg.Combo(values=["", "injection", "500|429", "bot"], size=(20,1), key="log_filter", enable_events=True, tooltip="Regex to filter with")
            ],
            [sg.T("  "*71+"↓")],
            # comboboxes
            [sg.Text("vhost/conf", font="bold"),
             sg.Combo(key="confn", size=(50,1), values=vhosts.list_vhosts(), enable_events=True, tooltip="Which *.conf to edit"),
             sg.Text("Log"),
             sg.Combo(key="logfn", values=logs.find_logs(), size=(30,1), enable_events=True, tooltip="Error/Audit log to show"),
             ],
        ]),

Changes to modseccfg/vhosts.py.

324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
...
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374

    # modsec: just a secrule without conditions
    def secaction(self, args):
        self.secrule(["@SecAction", "setvar:", args[0]])
    
    # modsec: SecRuleRemoveById 900001 900002 900003
    def secruleremovebyid(self, args):
        state = "❌"
        if self.wrap:  # record if within <Dir|File|If|Wrap> section
            state = ""
            #log.info("wrapped SecRuleRm", self.fn, self.wrap, args)
        for a in args:
            if re.match("^\d+-\d+$", a):   # are ranges still allowed?
                a = [int(x) for x in a.split("-")]
                for i in range(*a):
                    if i in rules:    # only apply state for known/existing rules, not the whole range()
                        self.rulestate[i] = state
................................................................................
    def _secruleupdate(self, cls, id, arg, *repl):
        if re.match("^\d+:\d$", id):
            id = float(id.replace(":", "."))
        elif re.match("^\d+$", id):
            id = int(id)
        else:
            return
        self.rulestate[id] = "➗"
        # We don't really use the detail. This is just to record that any one rule has been "modified".
        if repl:
            arg = f"!{repl[0]},{arg}"  # merge third parameter from `SecRuleUpdateTarget 123456 NEW_TARGET REMOVE_VAR`
        if not self.update.get(id):
            self.update[id] = {"vars":[], "actions":[]}
        self.update[id][cls].append(arg)
        







|

|







 







|







324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
...
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374

    # modsec: just a secrule without conditions
    def secaction(self, args):
        self.secrule(["@SecAction", "setvar:", args[0]])
    
    # modsec: SecRuleRemoveById 900001 900002 900003
    def secruleremovebyid(self, args):
        state = "🗶" #"❌"
        if self.wrap:  # record if within <Dir|File|If|Wrap> section
            state = "⋚" #""
            #log.info("wrapped SecRuleRm", self.fn, self.wrap, args)
        for a in args:
            if re.match("^\d+-\d+$", a):   # are ranges still allowed?
                a = [int(x) for x in a.split("-")]
                for i in range(*a):
                    if i in rules:    # only apply state for known/existing rules, not the whole range()
                        self.rulestate[i] = state
................................................................................
    def _secruleupdate(self, cls, id, arg, *repl):
        if re.match("^\d+:\d$", id):
            id = float(id.replace(":", "."))
        elif re.match("^\d+$", id):
            id = int(id)
        else:
            return
        self.rulestate[id] = "⋇" #"➗"
        # We don't really use the detail. This is just to record that any one rule has been "modified".
        if repl:
            arg = f"!{repl[0]},{arg}"  # merge third parameter from `SecRuleUpdateTarget 123456 NEW_TARGET REMOVE_VAR`
        if not self.update.get(id):
            self.update[id] = {"vars":[], "actions":[]}
        self.update[id][cls].append(arg)