Sindbad~EG File Manager
| Current Path : /opt/dedrads/ |
|
|
| Current File : //opt/dedrads/modsec_disable.py |
#!/usr/lib/rads/venv/bin/python3
"""Disable modsec rules for Cpanel or PlatformI users and domains."""
# Written by Quentin H
import re
import subprocess
import argparse
from pathlib import Path
import sys
import rads
from rads.color import red, green, yellow
PLATFORM_I = Path("/etc/ansible/wordpress-ultrastack")
def get_apache_version() -> str:
"""Get Apache version"""
try:
result = subprocess.run(
["apachectl", "-v"], capture_output=True, text=True, check=True
)
except FileNotFoundError:
print(red("apachectl not found. Please ensure Apache is installed."))
sys.exit(1)
except subprocess.CalledProcessError:
print(red("Failed to get Apache version."))
sys.exit(1)
if "Server version" in result.stdout:
version_line = result.stdout.split('\n')[0]
version = version_line.split()[2]
return version.split('/')[1]
print(red("Unable to parse Apache version."))
sys.exit(1)
def update_modsec_configuration(
modsec_file: Path,
rule_ids: list[int],
disable_all: bool,
reset: bool,
remove_rule_ids: list[int],
):
"""Update modsec.conf file based on provided arguments"""
modsec_file.parent.mkdir(
parents=True, exist_ok=True
) # Ensure the directory exists
# Ensure the modsec.conf file exists before modifying it
if not modsec_file.exists() or reset:
# Create or reset the file if necessary
modsec_file.write_text("", encoding="utf-8")
if disable_all:
modsec_file.write_text("SecRuleEngine Off\n")
elif rule_ids:
for rule_id in rule_ids:
disable_rule(modsec_file, rule_id)
elif remove_rule_ids:
for rule_id in remove_rule_ids:
remove_rule(modsec_file, rule_id)
def disable_rule(modsec_file: Path, rule_id: int) -> bool:
"""Add rules to modsec.conf, checks if the rule exists and skips it if
it does"""
rule_text = f"SecRuleRemoveById {rule_id}"
matcher = re.compile(fr"{re.escape(rule_text)}\b")
try:
with modsec_file.open("r+", encoding="utf-8") as file:
for line in file:
if line.startswith('SecRuleEngine Off'):
print(
red(
"Modsec is disabled for all rules, so not "
f"disabling {rule_id} in {modsec_file}."
)
)
return False
if matcher.match(line):
print(
red(
f"Rule {rule_id} already disabled, not "
f"adding to {modsec_file}."
)
)
return False
except FileNotFoundError:
pass
with modsec_file.open("a", encoding="utf-8") as file:
file.write(f"{rule_text}\n")
print(green(f"Rule {rule_id} added to file: {modsec_file}"))
return True
def remove_rule(modsec_file: Path, rule_id: int) -> bool:
"""Remove a rule from the modsec file"""
changed = False
rule_text = f"SecRuleRemoveById {rule_id}"
matcher = re.compile(fr"{re.escape(rule_text)}\b")
try:
with modsec_file.open("r+", encoding="utf-8") as file:
contents = file.readlines()
file.seek(0)
file.truncate()
for line in contents:
if matcher.match(line):
changed = True
else:
file.write(line)
except FileNotFoundError:
pass
if changed:
print(green(f"Rule {rule_id} removed from file: {modsec_file}"))
else:
print(yellow(f"Rule {rule_id} was not in file: {modsec_file}"))
return changed
def _reset(modsec_file: Path) -> bool:
changed = False
with modsec_file.open("r+", encoding="utf-8") as file:
contents = file.readlines()
file.seek(0)
file.truncate()
for line in contents:
if line.startswith("SecRuleRemoveById") or line.startswith(
"SecRuleEngine Off"
):
changed = True
else:
file.write(line)
return changed
def reset_modsec_configuration(modsec_file: Path) -> bool:
"""Remove all SecRuleRemoveById and SecRuleEngine entries from the modsec
file"""
try:
changed = _reset(modsec_file)
except FileNotFoundError:
changed = False
if changed:
print(
green(
"All SecRuleRemoveById and SecRuleEngine entries removed "
f"from file: {modsec_file}"
)
)
return changed
def cpanel_restart_apache():
"""Rebuild and restart Apache"""
subprocess.run(
["/usr/local/cpanel/bin/servers_queue", "queue", "build_apache_conf"],
check=False,
)
subprocess.run(
["/usr/local/cpanel/bin/servers_queue", "queue", "apache_restart"],
check=False,
)
print(green("Apache configuration and service reload queued."))
def platform_i_restart_apache():
try:
subprocess.run(["apachectl", "-t"], check=True)
except subprocess.CalledProcessError:
print(red("Syntax failed apache check"))
sys.exit(1)
try:
subprocess.run(["systemctl", "restart", "httpd"], check=True)
print(green("Apache restarted correctly."))
except subprocess.CalledProcessError:
print(red("Apache failed to restart"))
sys.exit(1)
def cpanel_run_ngxconf(user: str):
"""Run ngxconf if it exists"""
try:
subprocess.run(["/usr/bin/ngxconf", "-u", user, "-rd"], check=True)
except OSError:
pass # not installed; probably not an ngx server
except subprocess.CalledProcessError:
print(red(f"/usr/bin/ngxconf -u {user} -rd failed"))
def platform_i_modsec(args):
"""Function to handle platformI check and set the modsec file path"""
modsec_file_path = Path(
"/etc/httpd/modsecurity.d/activated_rules/z-mycustom.conf"
)
update_modsec_configuration(
modsec_file=modsec_file_path,
rule_ids=args.rule,
disable_all=args.off,
reset=args.reset,
remove_rule_ids=args.drop,
)
if args.reset:
reset_modsec_configuration(modsec_file_path)
platform_i_restart_apache()
def cpanel_modsec(args):
"""Cpanel function that sets the modsec.conf file based on the flags/args
passed to the script"""
if not args.user:
print(red("The --user (-u) flag is required for CPanel operations."))
sys.exit(1)
try:
apache_version = get_apache_version()
if apache_version.startswith("2.4"):
std_dir = Path("/usr/local/apache/conf/userdata/std/2_4")
ssl_dir = Path("/usr/local/apache/conf/userdata/ssl/2_4")
elif apache_version.startswith("2."):
std_dir = Path("/usr/local/apache/conf/userdata/std/2")
ssl_dir = Path("/usr/local/apache/conf/userdata/ssl/2")
else:
print(red(f"Unsupported Apache version {apache_version}."))
sys.exit(1)
# Determine the correct modsec_file path based on the presence of the
# domain flag
if args.domain:
std_conf: Path = std_dir / args.user / args.domain / "modsec.conf"
ssl_conf: Path = ssl_dir / args.user / args.domain / "modsec.conf"
else:
std_conf: Path = std_dir / args.user / "modsec.conf"
ssl_conf: Path = ssl_dir / args.user / "modsec.conf"
# Ensure the directory and files exist
std_conf.parent.mkdir(parents=True, exist_ok=True)
ssl_conf.parent.mkdir(parents=True, exist_ok=True)
std_conf.touch(exist_ok=True)
ssl_conf.touch(exist_ok=True)
changed = False
for rule_id in args.rule:
changed = disable_rule(std_conf, rule_id) or changed
changed = disable_rule(ssl_conf, rule_id) or changed
for rule_id in args.drop:
changed = remove_rule(std_conf, rule_id) or changed
changed = remove_rule(ssl_conf, rule_id) or changed
if args.reset:
if args.domain:
changed = reset_modsec_configuration(std_conf) or changed
changed = reset_modsec_configuration(ssl_conf) or changed
else:
changed = (
reset_modsec_configuration(
std_dir / args.user / "modsec.conf"
)
or changed
)
changed = (
reset_modsec_configuration(
ssl_dir / args.user / "modsec.conf"
)
or changed
)
if args.off:
changed = True
disable_all_rules(std_conf)
disable_all_rules(ssl_conf)
finally:
if changed:
cpanel_run_ngxconf(args.user)
cpanel_restart_apache()
else:
print(yellow("Skipping reload; nothing changed"))
def disable_all_rules(modsec_file: Path):
"""Function to disable all rules in modsec.conf"""
try:
_reset(modsec_file)
with modsec_file.open("a", encoding="utf-8") as file:
file.write("SecRuleEngine Off\n")
print(green(f"All rules disabled in file: {modsec_file}"))
except FileNotFoundError:
pass
def parse_args():
"""Parse sys.argv"""
# fmt: off
parser = argparse.ArgumentParser(description=__doc__)
if not PLATFORM_I.exists():
parser.add_argument(
"-u", "--user", type=str, required=True,
help="CPanel user this is for",
)
parser.add_argument(
"-d", "--domain", type=str,
help="The domain to modify rules for",
)
parser.add_argument(
"-a", "--all", action="store_true",
help="Apply changes to all domains for the user",
)
parser.add_argument(
"-o", "--off", action="store_true", help="Disable all rules"
)
parser.add_argument(
"-r", "--rule", type=int, nargs='+', default=[],
help="Specific rule IDs to disable",
)
parser.add_argument(
"-R", "--reset", action="store_true",
help="Reset the modsec configuration",
)
parser.add_argument(
"-D", "--drop", nargs='+', type=int, default=[],
help="Drop/Remove a specific rule ID from the modsec configuration"
)
# fmt: on
return parser.parse_args()
def main():
"""Main function to parse arguments and run needed functions based on
server type"""
args = parse_args()
try:
with rads.lock():
if PLATFORM_I.exists():
platform_i_modsec(args)
else:
cpanel_modsec(args)
except rads.LockError:
print(
red("Another instance of the script is currently running. Exiting.")
)
sys.exit(1)
if __name__ == "__main__":
main()
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists