Skip to content

Commit edd338a

Browse files
Update import_haproxy_waf.py
1 parent 068c4c5 commit edd338a

File tree

1 file changed

+116
-89
lines changed

1 file changed

+116
-89
lines changed

import_haproxy_waf.py

+116-89
Original file line numberDiff line numberDiff line change
@@ -2,134 +2,161 @@
22
import subprocess
33
import logging
44
from pathlib import Path
5+
import shutil
6+
import filecmp
7+
import time
58

6-
# Configure logging
7-
logging.basicConfig(
8-
level=logging.INFO,
9-
format="%(asctime)s - %(levelname)s - %(message)s",
10-
handlers=[logging.StreamHandler()],
11-
)
12-
13-
# Constants (configurable via environment variables)
14-
WAF_DIR = Path(os.getenv("WAF_DIR", "waf_patterns/haproxy")).resolve() # Source directory for WAF files
15-
HAPROXY_WAF_DIR = Path(os.getenv("HAPROXY_WAF_DIR", "/etc/haproxy/waf/")).resolve() # Target directory
16-
HAPROXY_CONF = Path(os.getenv("HAPROXY_CONF", "/etc/haproxy/haproxy.cfg")).resolve() # HAProxy config file
9+
# --- Configuration ---
10+
LOG_LEVEL = logging.INFO # DEBUG, INFO, WARNING, ERROR
11+
WAF_DIR = Path(os.getenv("WAF_DIR", "waf_patterns/haproxy")).resolve()
12+
HAPROXY_WAF_DIR = Path(os.getenv("HAPROXY_WAF_DIR", "/etc/haproxy/waf/")).resolve()
13+
HAPROXY_CONF = Path(os.getenv("HAPROXY_CONF", "/etc/haproxy/haproxy.cfg")).resolve()
14+
BACKUP_DIR = Path(os.getenv("BACKUP_DIR", "/etc/haproxy/waf_backup/")).resolve()
1715

1816
# HAProxy WAF configuration snippet
1917
WAF_CONFIG_SNIPPET = """
20-
# WAF and Bot Protection
18+
# WAF and Bot Protection (Generated by import_haproxy_waf.py)
2119
frontend http-in
2220
bind *:80
23-
default_backend web_backend
24-
acl bad_bot hdr_sub(User-Agent) -i waf/bots.acl
25-
acl waf_attack path_reg waf/waf.acl
26-
http-request deny if bad_bot
27-
http-request deny if waf_attack
21+
mode http
22+
option httplog
23+
# WAF and Bot Protection ACLs and Rules
24+
# Include generated ACL files
25+
include /etc/haproxy/waf/*.acl
2826
"""
27+
# --- Logging Setup ---
28+
logging.basicConfig(level=LOG_LEVEL, format="%(asctime)s - %(levelname)s - %(message)s")
29+
logger = logging.getLogger(__name__)
30+
2931

3032

3133
def copy_waf_files():
32-
"""
33-
Copy HAProxy WAF ACL files to the target directory.
34+
"""Copies WAF files, handling existing files, creating backups."""
35+
logger.info("Copying HAProxy WAF patterns...")
3436

35-
Raises:
36-
Exception: If there is an error copying files.
37-
"""
38-
logging.info("Copying HAProxy WAF patterns...")
37+
HAPROXY_WAF_DIR.mkdir(parents=True, exist_ok=True)
38+
BACKUP_DIR.mkdir(parents=True, exist_ok=True) # Ensure backup dir exists
3939

40-
try:
41-
# Ensure the target directory exists
42-
HAPROXY_WAF_DIR.mkdir(parents=True, exist_ok=True)
43-
logging.info(f"[+] Created or verified directory: {HAPROXY_WAF_DIR}")
44-
45-
# Copy ACL files
46-
for file in ["bots.acl", "waf.acl"]:
47-
src_path = WAF_DIR / file
48-
dst_path = HAPROXY_WAF_DIR / file
49-
50-
if not src_path.exists():
51-
logging.warning(f"[!] {file} not found in {WAF_DIR}")
52-
continue
53-
54-
try:
55-
subprocess.run(["cp", str(src_path), str(dst_path)], check=True)
56-
logging.info(f"[+] {file} copied to {HAPROXY_WAF_DIR}")
57-
except subprocess.CalledProcessError as e:
58-
logging.error(f"[!] Failed to copy {file}: {e}")
59-
raise
60-
except Exception as e:
61-
logging.error(f"[!] Error copying WAF files: {e}")
62-
raise
40+
for acl_file in WAF_DIR.glob("*.acl"): # Find all .acl files
41+
dst_path = HAPROXY_WAF_DIR / acl_file.name
6342

43+
try:
44+
if dst_path.exists():
45+
# Compare and backup if different
46+
if filecmp.cmp(acl_file, dst_path, shallow=False):
47+
logger.info(f"Skipping {acl_file.name} (identical file exists).")
48+
continue
49+
# Different file exists: backup
50+
backup_path = BACKUP_DIR / f"{dst_path.name}.{int(time.time())}"
51+
logger.warning(f"Existing {dst_path.name} differs. Backing up to {backup_path}")
52+
shutil.copy2(dst_path, backup_path) # Backup old file
6453

65-
def update_haproxy_conf():
66-
"""
67-
Ensure the WAF configuration snippet is included in haproxy.cfg.
54+
# Copy the (new or updated) file
55+
shutil.copy2(acl_file, dst_path)
56+
logger.info(f"Copied {acl_file.name} to {dst_path}")
57+
58+
except OSError as e:
59+
logger.error(f"Error copying {acl_file.name}: {e}")
60+
raise
6861

69-
Raises:
70-
Exception: If there is an error updating the HAProxy configuration.
71-
"""
72-
logging.info("Ensuring WAF patterns are included in haproxy.cfg...")
62+
63+
def update_haproxy_conf():
64+
"""Ensures the include statement is in haproxy.cfg, avoiding duplicates."""
65+
logger.info("Checking HAProxy configuration for WAF include...")
7366

7467
try:
75-
# Read the current configuration
7668
with open(HAPROXY_CONF, "r") as f:
77-
config = f.read()
78-
79-
# Append WAF configuration snippet if not present
80-
if WAF_CONFIG_SNIPPET.strip() not in config:
81-
logging.info("Adding WAF rules to haproxy.cfg...")
82-
with open(HAPROXY_CONF, "a") as f:
83-
f.write(WAF_CONFIG_SNIPPET)
84-
logging.info("[+] WAF rules added to haproxy.cfg.")
69+
config_lines = f.readlines()
70+
71+
# Check if the *exact* snippet is already present. We'll check for the
72+
# key parts of the snippet to be more robust.
73+
snippet_present = False
74+
for line in config_lines:
75+
if "include /etc/haproxy/waf/*.acl" in line:
76+
snippet_present = True
77+
break
78+
79+
if not snippet_present:
80+
# Find the 'frontend http-in' section
81+
frontend_start = -1
82+
for i, line in enumerate(config_lines):
83+
if line.strip().startswith("frontend http-in"):
84+
frontend_start = i
85+
break
86+
87+
if frontend_start == -1:
88+
logger.warning("No 'frontend http-in' section found. Appending to end of file.")
89+
with open(HAPROXY_CONF, "a") as f:
90+
f.write(f"\n{WAF_CONFIG_SNIPPET}\n")
91+
logger.info(f"Added WAF configuration snippet to {HAPROXY_CONF}")
92+
else:
93+
# Find the end of the 'frontend http-in' section
94+
frontend_end = -1
95+
for i in range(frontend_start + 1, len(config_lines)):
96+
if line.strip() == "" or not line.startswith(" "): # Check it is part of the config
97+
frontend_end = i
98+
break
99+
100+
101+
if frontend_end == -1:
102+
frontend_end = len(config_lines) # End of file
103+
104+
# Insert the include statement *within* the frontend section.
105+
config_lines.insert(frontend_end, " include /etc/haproxy/waf/*.acl\n")
106+
107+
# Write the modified configuration back to the file
108+
with open(HAPROXY_CONF, "w") as f:
109+
f.writelines(config_lines)
110+
logger.info(f"Added WAF include to 'frontend http-in' section in {HAPROXY_CONF}")
85111
else:
86-
logging.info("WAF patterns already included in haproxy.cfg.")
87-
except Exception as e:
88-
logging.error(f"[!] Error updating HAProxy configuration: {e}")
112+
logger.info("WAF include statement already present.")
113+
114+
except FileNotFoundError:
115+
logger.error(f"HAProxy configuration file not found: {HAPROXY_CONF}")
116+
raise
117+
except OSError as e:
118+
logger.error(f"Error updating HAProxy configuration: {e}")
89119
raise
90120

91121

92122
def reload_haproxy():
93-
"""
94-
Reload HAProxy to apply the new WAF rules.
95-
96-
Raises:
97-
Exception: If there is an error reloading HAProxy.
98-
"""
99-
logging.info("Testing HAProxy configuration...")
123+
"""Tests the HAProxy configuration and reloads if valid."""
124+
logger.info("Reloading HAProxy...")
100125

101126
try:
102-
# Test HAProxy configuration
103-
subprocess.run(["haproxy", "-c", "-f", str(HAPROXY_CONF)], check=True)
104-
logging.info("[+] HAProxy configuration test passed.")
127+
# Test configuration
128+
result = subprocess.run(["haproxy", "-c", "-f", str(HAPROXY_CONF)],
129+
capture_output=True, text=True, check=True)
130+
logger.info(f"HAProxy configuration test successful:\n{result.stdout}")
131+
105132

106133
# Reload HAProxy
107-
subprocess.run(["systemctl", "reload", "haproxy"], check=True)
108-
logging.info("[+] HAProxy reloaded successfully.")
134+
result = subprocess.run(["systemctl", "reload", "haproxy"],
135+
capture_output=True, text=True, check=True)
136+
logger.info("HAProxy reloaded.")
137+
138+
109139
except subprocess.CalledProcessError as e:
110-
logging.error(f"[!] HAProxy configuration test failed: {e}")
111-
raise
140+
logger.error(f"HAProxy command failed: {e.cmd} - Return code: {e.returncode}")
141+
logger.error(f"Stdout: {e.stdout}")
142+
logger.error(f"Stderr: {e.stderr}")
143+
raise # Re-raise to signal failure
112144
except FileNotFoundError:
113-
logging.error("[!] 'haproxy' or 'systemctl' command not found. Are you on a supported system?")
114-
raise
115-
except Exception as e:
116-
logging.error(f"[!] Error reloading HAProxy: {e}")
145+
logger.error("'haproxy' or 'systemctl' command not found. Is HAProxy/systemd installed?")
117146
raise
118147

119148

120149
def main():
121-
"""
122-
Main function to execute the script.
123-
"""
150+
"""Main function."""
124151
try:
125152
copy_waf_files()
126153
update_haproxy_conf()
127154
reload_haproxy()
128-
logging.info("[✔] HAProxy configured with latest WAF rules.")
155+
logger.info("HAProxy WAF configuration updated successfully.")
129156
except Exception as e:
130-
logging.critical(f"[!] Script failed: {e}")
157+
logger.critical(f"Script failed: {e}")
131158
exit(1)
132159

133160

134161
if __name__ == "__main__":
135-
main()
162+
main()

0 commit comments

Comments
 (0)