GUI editor to tame mod_security rules

⌈⌋ branch:  modseccfg


Artifact [0ee9bf69c4]

Artifact 0ee9bf69c4ccb14ee5b34c21fb2f862faab49fd87084d43651aa16553d2fd6d1:

  • Executable file dev/directives2pmd.py — part of check-in [e9e02ee8f4] at 2020-11-18 21:35:01 on branch trunk — Use manual as SecOptions source (incomplete, but yields more details) (user: mario size: 2742)

#!/usr/bin/env python3
# encoding: utf-8
# api: python
# description: convert directives.* to plugin meta data struct

import re, json
from collections import OrderedDict

src1 = open("./dev/directives.conf", "r").read()
src2 = open("./dev/directives.md", "r").read()


alts = {
    "Serial": "Concurrent",
    "Native": "JSON"
}
skip = "^(SecAction|SecRule|SecRule(Remove|Update)\w+|SecRuleScript|SecRemoteRules|SecMarker)$"

# out
pmd1 = OrderedDict()
pmd2 = OrderedDict()



# read from sample .conf
rx1 = re.compile("""
    (  (?:^\# .* \\n)+  )
    ^(Sec\w+) \s+ (.+)$
""", re.X|re.M|re.I)

for doc, dir, val in rx1.findall(""):
#    print(doc,dir,val)
    
    if re.match(skip, dir, re.I):
        continue

    doc = re.sub("^\#\s+", "", doc, 0, re.M)
    desc = re.findall("^(.+)(?:\.|,|\(|$|\\n)", doc)
    
    p = {
        "name": dir,
        "description": desc[0] if len(desc) else dir,
        "type": "str",
        "value": "", #  ← we don't want default values
        "help": doc
    }
    
    if re.match("^\d+$", val):
        p["type"] = "str"   #← we don't actually want type mapping
    elif re.match("On|Off|Det\w+", val):
        p["type"] = "select"
        p["select"] = "On|Off|"+val
    elif re.match("Proc\w+|Serial|Reject|Relevant\w+", val):
        p["type"] = "select"
        p["select"] = val+"|" +alts.get(val, "Off")
    
    pmd1[p["name"]] = p


# manual wiki extract
# The colons : are not very consistent around '''
rx2 = re.compile("""
    ^==\s*(\w+)\s*==
    \s*
    '''Description:?''':? \s* (.+)
    \s*
    '''[\w\s]+:?''':? \s <code>(.+)</code>
    (?:\s*'''(?!NOTE)[\w\s]+:?''':?.*)+
    \s*
    ^ (?:'''NOTE:?''':?\s*)?            # pick either NOTE
         (\w.*  (?: \s*^\w.+)* )?       # or trailing paragraph, if any
""", re.X|re.M|re.I)

for dir, desc, code,doc  in rx2.findall(src2):
#    print(dir,desc,code)
    if re.match(skip, dir, re.I):
        continue

    p = {
        "name": dir,
        "description": desc,
        "type": "str",
        "value": "",  # val,   ← we don't want default values
        "help": doc
    }
    
    if re.search("\|", code):
        code,val=code.split(" ", 1)
        p["type"] = "select"
        p["select"] = val.strip()


    pmd2[p["name"]] = p
 

# add default values from sample .conf
for name,p in pmd1.items():
    if name in pmd2:
        pmd2[name]["value"] = p["value"]
    
# postprocess
for name,p in pmd2.items():
    if "select" in p:
        k = p["select"].split("|")
        p["select"] = dict(zip(k,k))
    

    
#std
#print(json.dumps(pmd, indent=4))

# write as ordereddict
print("options = OrderedDict()")
for k,d in pmd2.items():
    print(f"options['{k}'] = " + json.dumps(d, indent=4))

#print(len(pmd2))