import re LYNIS_REGEX = { "green": r"\[1;32m", # more advanced "green": r"\[ .*\[1;32m([\w\d ]*).*", "yellow": r"\[1;33m", "red": r"\[1;31m", "white": r"\[1;37m", "heading": r".*\[1;33m([\w\d ,:-]*).*" } LYNIS_BLOCKS = { "Boot and services", "Kernel", "Memory and Processes", "Users, Groups and Authentication", "Shells", "File systems", "USB Devices", "Storage", "NFS", "Name services", "Ports and packages", "Networking", "Printers and Spools", "Software: e-mail and messaging", "Software: firewalls", "Software: webserver", "SSH Support", "Databases", "PHP", "Logging and files", "Insecure services", "Scheduled tasks", "Accounting", "Time and Synchronization", "Cryptography", "Security frameworks", "Software: Malware", "File Permissions", "Home directories", "Kernel Hardening", "Hardening" } def lynis_get_base(path_to_log): with open(path_to_log) as handle: text = handle.read() blocks = text.split("[+]") interesting_blocks = {} for block in blocks: heading = re.findall(LYNIS_REGEX["heading"], block.splitlines()[0]) if heading and heading[0] in LYNIS_BLOCKS: block_text = "".join(block.splitlines()[2:]) interesting_blocks[heading[0]] = { "text": block_text, "counts": { "green": len(re.findall(LYNIS_REGEX['green'], block_text)), "yellow": len(re.findall(LYNIS_REGEX['yellow'], block_text)), "red": len(re.findall(LYNIS_REGEX['red'], block_text)), "white": len(re.findall(LYNIS_REGEX['white'], block_text)), } } interesting_blocks["GENERAL"] = { "warningCount": None, "suggestionCount": None, "hardeningIndex": None, "testsPerformed": None } if warning_count := re.findall(r".*Warnings.* \((\d+)\)", text): interesting_blocks["GENERAL"]["warningCount"] = int(warning_count[0]) if suggestion_count := re.findall(r".*Suggestions.* \((\d+)\)", text): interesting_blocks["GENERAL"]["suggestionCount"] = int(suggestion_count[0]) if hardening_index := re.findall(r".*Hardening index.*m(\d+)", text): interesting_blocks["GENERAL"]["hardeningIndex"] = int(hardening_index[0]) if tests_performed := re.findall(r".*Tests performed.*m(\d+)", text): interesting_blocks["GENERAL"]["testsPerformed"] = int(tests_performed[0]) return interesting_blocks def lynis_add_special_properties(lynis_base): def find_prop(category, key, regex, as_int=False): if category in lynis_base: if match := re.findall(regex, lynis_base[category]["text"]): lynis_base[category][key] = int(match[0]) if as_int else match[0] find_prop("Boot and services", "runningServices", r"found (\d+) running services", as_int=True) find_prop("Boot and services", "enabledServices", r"found (\d+) enabled services", as_int=True) find_prop("Kernel", "defaultRunLevel", r"RUNLEVEL (\d+)", as_int=True) find_prop("Kernel", "activeModules", r"Found (\d+) active modules", as_int=True) find_prop("Kernel", "kernelUpdate", r"Checking for available kernel update.*? \[ .*?m([\w ]*).*? \]") find_prop("Shells", "numShells", r"Result: found (\d*) shells", as_int=True) find_prop("Networking", "ipv6Enabled", r"Checking IPv6 configuration.*? \[ .*?m([\w ]*).*? \]") find_prop("Time and Synchronization", "lastSync", r"Last time synchronization.*? \[ .*?m([\w ]*).*? \]") find_prop("Security frameworks", "unconfinedProcesses", r"Found (\d+) unconfined processes") def lynis_remove_text_from_blocks(lynis_base): for block in lynis_base: try: del lynis_base[block]["text"] except KeyError: pass def lynis_parse(path): lynis_base = lynis_get_base(path) lynis_add_special_properties(lynis_base) lynis_remove_text_from_blocks(lynis_base) return lynis_base