GUI editor to tame mod_security rules

⌈⌋ branch:  modseccfg


Check-in [0f1d190f43]

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

Overview
Comment:Add basic plugin_load(), generilize `add_menu()` into `init()`
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 0f1d190f43bf7a0ddff2fc85e874507b9bf3b5b77e072b0489f47f5402ecbe12
User & Date: mario 2020-12-26 22:38:14
Context
2020-12-28
12:59
Add docs for logfmt1 check-in: 33aecba645 user: mario tags: trunk
2020-12-26
22:38
Add basic plugin_load(), generilize `add_menu()` into `init()` check-in: 0f1d190f43 user: mario tags: trunk
22:37
Use radiobuttons for exclusive actions (deny/block), move pause into params section, fix SecRuleCombined to use deepcopy (instead of duplicating flags in global rule) check-in: 129ce9633e user: mario tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to README.md.

1
2
3
4
5
6
7
8
## modseccfg

 * GUI to define SecRuleRemoveById settings on a vhost-basis
 * Tries to suggest false positives from error and audit logs
 * And configure mod_security and CoreRuleSet variables.
 * Runs locally, via `ssh -X` forwarding, or per `modseccfg ssh:/`
   remoting.

|







1
2
3
4
5
6
7
8
## mod_security config GUI

 * GUI to define SecRuleRemoveById settings on a vhost-basis
 * Tries to suggest false positives from error and audit logs
 * And configure mod_security and CoreRuleSet variables.
 * Runs locally, via `ssh -X` forwarding, or per `modseccfg ssh:/`
   remoting.

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

### from `project` import `meta`

| meta           | info                                                            |
|:---------------|:----------------------------------------------------------------|
| depends        | python:[pysimplegui](https://pypi.org/project/PySimpleGUI/), python:[pluginconf](https://pypi.org/project/pluginconf/), python:[tkinter](https://docs.python.org/3/library/tkinter.html), sys:[mod-security](https://packages.debian.org/sid/libapache2-mod-security2), bin:[sshfs](https://packages.debian.org/sid/sshfs)  |
| compat         | Python ≥3.6, Apache 2.x, mod_security 2.9.x, CRS 3.x, BSD/Linux |
| compliancy     | xdg, pluginspec, !pep8, !logfmt, !desktop, !xdnd, !mallard, sshrc, !netrc, !http_proxy, !nobackup, !PKG_INFO, !releases.json, !doap, !packfile |
| system usage   | opportune shell invokes (sshfs, find, cat, dpkg, xdg-open)      |
| paths          | ~/mnt/,  ~/backup-config/, ~/.config/modseccfg/                 |
| testing        | few data-driven assertions, only manual UI and usage tests      |
| docs           | minimal wiki, news, no man page                                 |
| activity       | burst, temporary                                                |
| state          | beta                                                            |
| support        | `None`                                                          |
| contrib        | mail, fossil DVCS (create an account or send bundles)           |
| announce       | [freshcode.club](https://freshcode.club/projects/modseccfg), pypi.org    |









|











78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

### from `project` import `meta`

| meta           | info                                                            |
|:---------------|:----------------------------------------------------------------|
| depends        | python:[pysimplegui](https://pypi.org/project/PySimpleGUI/), python:[pluginconf](https://pypi.org/project/pluginconf/), python:[tkinter](https://docs.python.org/3/library/tkinter.html), sys:[mod-security](https://packages.debian.org/sid/libapache2-mod-security2), bin:[sshfs](https://packages.debian.org/sid/sshfs)  |
| compat         | Python ≥3.6, Apache 2.x, mod_security 2.9.x, CRS 3.x, BSD/Linux |
| compliancy     | xdg, pluginspec, !pep8, logfmt, !desktop, !xdnd, !mallard, sshrc, !netrc, !http_proxy, !nobackup, !PKG_INFO, !releases.json, !doap, !packfile |
| system usage   | opportune shell invokes (sshfs, find, cat, dpkg, xdg-open)      |
| paths          | ~/mnt/,  ~/backup-config/, ~/.config/modseccfg/                 |
| testing        | few data-driven assertions, only manual UI and usage tests      |
| docs           | minimal wiki, news, no man page                                 |
| activity       | burst, temporary                                                |
| state          | beta                                                            |
| support        | `None`                                                          |
| contrib        | mail, fossil DVCS (create an account or send bundles)           |
| announce       | [freshcode.club](https://freshcode.club/projects/modseccfg), pypi.org    |


Changes to modseccfg/install/__init__.py.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from modseccfg.utils import srvroot, conf
import PySimpleGUI as sg

dir = re.sub("[^/]+$", "", __file__)


# hook: mainwindow.add_menu
def add_menu(menu):
    print("\n")
    m = menu[0][1] # File
    i = 5 # m.index("Test")
    m.insert(i, ls())
    m.insert(i, "Install")
    
# find files
def ls():







|
<







15
16
17
18
19
20
21
22

23
24
25
26
27
28
29
from modseccfg.utils import srvroot, conf
import PySimpleGUI as sg

dir = re.sub("[^/]+$", "", __file__)


# hook: mainwindow.add_menu
def init(menu, **kwargs):

    m = menu[0][1] # File
    i = 5 # m.index("Test")
    m.insert(i, ls())
    m.insert(i, "Install")
    
# find files
def ls():

Changes to modseccfg/mainwindow.py.

168
169
170
171
172
173
174
175

176
177
178
179
180
181
182
183
184
185
#-- GUI event loop and handlers
class gui_event_handler:

    # prepare window
    def __init__(self):
        #-- hooks
        log.init.info("scan plugins")
        self.plugins = [recipe, install, scripts]

        for mod in self.plugins:
            if hasattr(mod, "add_menu"):
                mod.add_menu(menu)
        
        #-- build
        gui_event_handler.mainwindow = self
        log.init.info("build window")
        self.w = sg.Window(
            title=f"mod_security config {utils.srvroot.srv}", layout=layout, font="Sans 12",
            size=(1200,825), return_keyboard_events=conf.keyboard_binds, resizable=1, icon=icons.icon







|
>

|
|







168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#-- GUI event loop and handlers
class gui_event_handler:

    # prepare window
    def __init__(self):
        #-- hooks
        log.init.info("scan plugins")
        self.plugins = [recipe, install, scripts] + utils.load_plugins()
        #print(self.plugins)
        for mod in self.plugins:
            if hasattr(mod, "init"):
                mod.init(menu=menu, layout=layout, mainwindow=self)
        
        #-- build
        gui_event_handler.mainwindow = self
        log.init.info("build window")
        self.w = sg.Window(
            title=f"mod_security config {utils.srvroot.srv}", layout=layout, font="Sans 12",
            size=(1200,825), return_keyboard_events=conf.keyboard_binds, resizable=1, icon=icons.icon

Changes to modseccfg/scripts/__init__.py.

204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
            scripts[meta["title"]] = meta
            c = meta.get("category").title()
                          # create and/or append
            menu[c] = menu.get(c, []) + [meta["title"]]
    return menu            

# inject into mainwindow.menu/layout
def add_menu(menu):
    if not conf.get("script_sep_menus"):
        menu = menu[3][1] # submenus under ["Log", [→]]
    for group, files in scan_files().items():
        add = [group, sorted(files)]
        if conf.get("script_sep_menus"): add = [add]
        menu += add

# satisfy event lookup from mainwindow
def has(title):
    return title in scripts








|











204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
            scripts[meta["title"]] = meta
            c = meta.get("category").title()
                          # create and/or append
            menu[c] = menu.get(c, []) + [meta["title"]]
    return menu            

# inject into mainwindow.menu/layout
def init(menu, **kwargs):
    if not conf.get("script_sep_menus"):
        menu = menu[3][1] # submenus under ["Log", [→]]
    for group, files in scan_files().items():
        add = [group, sorted(files)]
        if conf.get("script_sep_menus"): add = [add]
        menu += add

# satisfy event lookup from mainwindow
def has(title):
    return title in scripts

Changes to modseccfg/utils.py.

25
26
27
28
29
30
31

32
33
34
35
36
37
38
39
40
41
42
43
44
45

import sys
import os
import pathlib
import re
import functools
import subprocess

import atexit
import json
import pluginconf, pluginconf.gui
import appdirs
try: import frosch; frosch.hook()
except: pass
import logging, inspect


#-- dict mirrored into object properties
class DictObj(dict):
    def __init__(self, dict={}):
        self.__dict__ = self
        self.update(dict)







>






|







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

import sys
import os
import pathlib
import re
import functools
import subprocess
import importlib
import atexit
import json
import pluginconf, pluginconf.gui
import appdirs
try: import frosch; frosch.hook()
except: pass
import logging, inspect, traceback


#-- dict mirrored into object properties
class DictObj(dict):
    def __init__(self, dict={}):
        self.__dict__ = self
        self.update(dict)
85
86
87
88
89
90
91











92
93
94
95
96
97
98

#-- plugin lookup
pluginconf.module_base = __name__
pluginconf.plugin_base = [__package__]
for module,meta in pluginconf.all_plugin_meta().items():
    pluginconf.add_plugin_defaults(conf, conf.plugins, meta, module)













#-- path
def expandpath(dir):
    return str(pathlib.Path(dir).expanduser())

#-- @decorator to override module function
@functools.singledispatch







>
>
>
>
>
>
>
>
>
>
>







86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

#-- plugin lookup
pluginconf.module_base = __name__
pluginconf.plugin_base = [__package__]
for module,meta in pluginconf.all_plugin_meta().items():
    pluginconf.add_plugin_defaults(conf, conf.plugins, meta, module)

# invoked from main
def load_plugins():
    add = []
    for name, state in conf.plugins.items():
        module_name = f"modseccfg.{name}"
        if state and not name.startswith("_") and not module_name in sys.modules:
            try:
                add.append(importlib.import_module(module_name))
            except:
                log.error(traceback.format_exc())
    return add

#-- path
def expandpath(dir):
    return str(pathlib.Path(dir).expanduser())

#-- @decorator to override module function
@functools.singledispatch

Changes to requirements.txt.

1
2
3

4
5
pysimplegui
python3-tk
pluginconf

appdirs
frosch



>


1
2
3
4
5
6
pysimplegui
python3-tk
pluginconf
logfmt1
appdirs
frosch