patterns/import_nginx_waf.py

152 lines
5.4 KiB
Python
Raw Normal View History

2024-12-21 09:05:16 +01:00
import os
import subprocess
import logging
2025-01-03 13:17:10 +01:00
from pathlib import Path
2025-02-28 11:22:21 +01:00
import shutil
import filecmp
import time
2024-12-21 09:05:16 +01:00
2025-02-28 11:22:21 +01:00
# --- Configuration ---
LOG_LEVEL = logging.INFO # DEBUG, INFO, WARNING, ERROR
WAF_DIR = Path(os.getenv("WAF_DIR", "waf_patterns/nginx")).resolve()
NGINX_WAF_DIR = Path(os.getenv("NGINX_WAF_DIR", "/etc/nginx/waf/")).resolve()
NGINX_CONF = Path(os.getenv("NGINX_CONF", "/etc/nginx/nginx.conf")).resolve()
BACKUP_DIR = Path(os.getenv("BACKUP_DIR", "/etc/nginx/waf_backup/")).resolve()
INCLUDE_STATEMENT = "include /etc/nginx/waf/*.conf;"
2025-01-03 13:17:10 +01:00
2025-02-28 11:22:21 +01:00
# --- Logging Setup ---
logging.basicConfig(level=LOG_LEVEL, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
2024-12-21 09:05:16 +01:00
2025-02-28 11:22:21 +01:00
def copy_waf_files():
"""Copies WAF files, handling existing files, and creating backups."""
logger.info("Copying Nginx WAF patterns...")
2025-01-03 13:17:10 +01:00
2025-02-28 11:22:21 +01:00
NGINX_WAF_DIR.mkdir(parents=True, exist_ok=True)
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
2025-01-03 13:17:10 +01:00
2025-02-28 11:22:21 +01:00
for conf_file in WAF_DIR.glob("*.conf"):
dst_path = NGINX_WAF_DIR / conf_file.name
2025-01-03 13:17:10 +01:00
2025-02-28 11:22:21 +01:00
try:
2025-01-03 13:17:10 +01:00
if dst_path.exists():
2025-02-28 11:22:21 +01:00
# Compare and backup if different
if filecmp.cmp(conf_file, dst_path, shallow=False):
logger.info(f"Skipping {conf_file.name} (identical file exists).")
continue
# Backup different file
backup_path = BACKUP_DIR / f"{dst_path.name}.{int(time.time())}"
logger.warning(f"Existing {dst_path.name} differs. Backing up to {backup_path}")
shutil.copy2(dst_path, backup_path)
2025-01-03 13:17:10 +01:00
2025-02-28 11:22:21 +01:00
# Copy the (new/updated) file
shutil.copy2(conf_file, dst_path)
logger.info(f"Copied {conf_file.name} to {dst_path}")
except OSError as e:
logger.error(f"Error copying {conf_file.name}: {e}")
raise
2024-12-21 09:05:16 +01:00
2025-02-28 11:22:21 +01:00
def update_nginx_conf():
"""Ensures the include directive is present in nginx.conf (http context)."""
logger.info("Checking Nginx configuration for WAF include...")
2024-12-21 09:05:16 +01:00
2025-01-03 13:17:10 +01:00
try:
with open(NGINX_CONF, "r") as f:
2025-02-28 11:22:21 +01:00
config_lines = f.readlines()
# Check if the include statement is already present.
include_present = any(INCLUDE_STATEMENT in line for line in config_lines)
if not include_present:
# Find the 'http' block. This is where the include belongs.
http_start = -1
for i, line in enumerate(config_lines):
if line.strip().startswith("http {"):
http_start = i
break
if http_start == -1:
# No http block found. Add the include *and* the http block.
logger.warning("No 'http' block found. Adding to end of file.")
with open(NGINX_CONF, "a") as f:
f.write(f"\nhttp {{\n {INCLUDE_STATEMENT}\n}}\n")
logger.info(f"Added 'http' block and WAF include to {NGINX_CONF}")
return
# Find the end of the 'http' block
http_end = -1
for i in range(http_start + 1, len(config_lines)):
if line.strip() == "}":
http_end = i
break
if http_end == -1:
# Malformed config? Shouldn't happen, but handle it.
http_end = len(config_lines)
logger.warning("Malformed Nginx config (no closing brace for 'http' block).")
# Insert the include statement *within* the http block.
config_lines.insert(http_end, f" {INCLUDE_STATEMENT}\n")
# Write the modified configuration back.
with open(NGINX_CONF, "w") as f:
f.writelines(config_lines)
logger.info(f"Added WAF include to 'http' block in {NGINX_CONF}")
2025-01-03 13:17:10 +01:00
else:
2025-02-28 11:22:21 +01:00
logger.info("WAF include statement already present.")
except FileNotFoundError:
logger.error(f"Nginx configuration file not found: {NGINX_CONF}")
raise
except OSError as e:
logger.error(f"Error updating Nginx configuration: {e}")
2025-01-03 13:17:10 +01:00
raise
2024-12-21 09:05:16 +01:00
def reload_nginx():
2025-02-28 11:22:21 +01:00
"""Tests the Nginx configuration and reloads if valid."""
logger.info("Reloading Nginx...")
2025-01-03 13:17:10 +01:00
try:
2025-02-28 11:22:21 +01:00
# Test configuration
result = subprocess.run(["nginx", "-t"],
capture_output=True, text=True, check=True)
logger.info(f"Nginx configuration test successful:\n{result.stdout}")
2025-01-03 13:17:10 +01:00
# Reload Nginx
2025-02-28 11:22:21 +01:00
result = subprocess.run(["systemctl", "reload", "nginx"],
capture_output=True, text=True, check=True)
logger.info("Nginx reloaded.")
2025-01-03 13:17:10 +01:00
except subprocess.CalledProcessError as e:
2025-02-28 11:22:21 +01:00
logger.error(f"Nginx command failed: {e.cmd} - Return code: {e.returncode}")
logger.error(f"Stdout: {e.stdout}")
logger.error(f"Stderr: {e.stderr}")
raise # Re-raise to signal failure
2025-01-03 13:17:10 +01:00
except FileNotFoundError:
2025-02-28 11:22:21 +01:00
logger.error("'nginx' or 'systemctl' command not found. Is Nginx/systemd installed?")
2025-01-03 13:17:10 +01:00
raise
2025-02-28 11:22:21 +01:00
except Exception as e: # added extra exception
logger.error(f"[!] Error reloading Nginx: {e}")
2025-01-03 13:17:10 +01:00
raise
def main():
2025-02-28 11:22:21 +01:00
"""Main function."""
2025-01-03 13:17:10 +01:00
try:
copy_waf_files()
update_nginx_conf()
reload_nginx()
2025-02-28 11:22:21 +01:00
logger.info("Nginx WAF configuration updated successfully.")
2025-01-03 13:17:10 +01:00
except Exception as e:
2025-02-28 11:22:21 +01:00
logger.critical(f"Script failed: {e}")
2025-01-03 13:17:10 +01:00
exit(1)
2024-12-21 09:05:16 +01:00
if __name__ == "__main__":
2025-02-28 11:22:21 +01:00
main()