diff --git a/utils/haproxy_config.py b/utils/haproxy_config.py index ad6505c..df18e6a 100644 --- a/utils/haproxy_config.py +++ b/utils/haproxy_config.py @@ -76,9 +76,84 @@ def count_frontends_and_backends(): return frontend_count, backend_count, acl_count, layer7_count, layer4_count def sanitize_name(name): - """Convert hostname/name to valid ACL name (replace special chars with underscores)""" + """Convert hostname/name to valid ACL name""" return name.replace('.', '_').replace('-', '_').replace('/', '_').replace(':', '_') +def frontend_exists_at_port(frontend_ip, frontend_port): + """Check if frontend already exists at specific port""" + if not os.path.exists(HAPROXY_CFG): + return None + + try: + with open(HAPROXY_CFG, 'r') as f: + content = f.read() + lines = content.split('\n') + + for i, line in enumerate(lines): + if line.strip().startswith('frontend'): + frontend_name = line.strip().split(' ', 1)[1] + for j in range(i+1, min(i+10, len(lines))): + if lines[j].strip().startswith('bind'): + bind_info = lines[j].strip().split(' ', 1)[1] + if f"{frontend_ip}:{frontend_port}" in bind_info: + return frontend_name + elif lines[j].strip().startswith('frontend') or lines[j].strip().startswith('backend'): + break + except Exception as e: + print(f"[HAPROXY_CONFIG] Error: {e}", flush=True) + + return None + +def add_acl_to_frontend(frontend_name, acl_name, hostname, backend_name): + """Dodaj ACL i use_backend do istniejącego frontendu""" + if not os.path.exists(HAPROXY_CFG): + return False + + try: + with open(HAPROXY_CFG, 'r') as f: + content = f.read() + + lines = content.split('\n') + + frontend_idx = -1 + for i, line in enumerate(lines): + if line.strip().startswith('frontend') and frontend_name in line: + frontend_idx = i + break + + if frontend_idx == -1: + return False + + insert_idx = -1 + for i in range(frontend_idx + 1, len(lines)): + if lines[i].strip().startswith('backend'): + insert_idx = i + break + if 'use_backend' in lines[i] or 'default_backend' in lines[i]: + insert_idx = i + break + + if insert_idx == -1: + insert_idx = len(lines) - 1 + + acl_line = f" acl {acl_name} hdr(host) -i {hostname}\n" + use_backend_line = f" use_backend {backend_name} if {acl_name}\n" + + for line in lines[frontend_idx:insert_idx]: + if acl_name in line and 'acl' in line: + return True + + lines.insert(insert_idx, use_backend_line) + lines.insert(insert_idx, acl_line) + + with open(HAPROXY_CFG, 'w') as f: + f.write('\n'.join(lines)) + + return True + except Exception as e: + print(f"[HAPROXY_CONFIG] Error adding ACL: {e}", flush=True) + return False + def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, protocol, backend_name, backend_servers, health_check, health_check_tcp, health_check_link, sticky_session, add_header, header_name, header_value, sticky_session_type, is_acl, acl_name, @@ -93,7 +168,6 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, os.makedirs(os.path.dirname(HAPROXY_CFG), exist_ok=True) - # Generate unique backend name with hostname suffix unique_backend_name = f"{backend_name}_{sanitize_name(frontend_hostname)}" if frontend_hostname else backend_name if is_backend_exist(unique_backend_name): @@ -104,14 +178,61 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, backend_servers = backend_servers[:1] try: + # ===== CHECK IF FRONTEND EXISTS AT PORT ===== + existing_frontend = frontend_exists_at_port(frontend_ip, frontend_port) + + if existing_frontend: + # Frontend już istnieje - dodaj tylko backend i ACL + unique_backend_name = f"{backend_name}_{sanitize_name(frontend_hostname)}" if frontend_hostname else backend_name + + # Utwórz backend + with open(HAPROXY_CFG, 'a') as haproxy_cfg: + haproxy_cfg.write(f"\nbackend {unique_backend_name}\n") + + if not is_no_lb: + haproxy_cfg.write(f" balance {lb_method}\n") + + if sticky_session and not is_no_lb: + if sticky_session_type == "cookie": + haproxy_cfg.write(f" cookie SERVERID insert indirect nocache\n") + elif sticky_session_type == "source": + haproxy_cfg.write(f" stick-table type ip size 200k expire 30m\n") + haproxy_cfg.write(f" stick on src\n") + + if health_check and protocol == 'http': + haproxy_cfg.write(f" option httpchk GET {health_check_link}\n") + elif health_check_tcp and protocol == 'tcp': + haproxy_cfg.write(f" option tcp-check\n") + + if add_header: + haproxy_cfg.write(f" http-response add-header {header_name} {header_value}\n") + + if del_server_header: + haproxy_cfg.write(f" http-response del-header Server\n") + + if forward_for: + haproxy_cfg.write(f" option forwardfor\n") + + for server_name, server_ip, server_port, maxconn in backend_servers: + maxconn_str = f" maxconn {maxconn}" if maxconn else "" + + if health_check and protocol == 'http': + haproxy_cfg.write(f" server {server_name} {server_ip}:{server_port}{maxconn_str} check\n") + else: + haproxy_cfg.write(f" server {server_name} {server_ip}:{server_port}{maxconn_str}\n") + + acl_name_sanitized = f"is_{sanitize_name(frontend_hostname)}" if frontend_hostname else f"is_{unique_backend_name}" + add_acl_to_frontend(existing_frontend, acl_name_sanitized, frontend_hostname or 'localhost', unique_backend_name) + + return f"Backend added to existing frontend {existing_frontend}" + + # ===== TWORZENIE NOWEGO FRONTENDU ===== with open(HAPROXY_CFG, 'a') as haproxy_cfg: - # ===== PRIMARY FRONTEND ===== haproxy_cfg.write(f"\nfrontend {frontend_name}\n") if is_frontend_exist(frontend_name, frontend_ip, frontend_port): return "Frontend or Port already exists. Cannot add duplicate." - # Bind line haproxy_cfg.write(f" bind {frontend_ip}:{frontend_port}") if use_ssl: @@ -119,36 +240,33 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, haproxy_cfg.write("\n") - # ===== SET HEADERS (RIGHT AFTER BIND/CERT) ===== + # Headers ZARAZ PO BIND/CERT haproxy_cfg.write(f" http-request set-header X-Forwarded-For %[src]\n") if use_ssl: haproxy_cfg.write(f" http-request set-header X-Forwarded-Proto https\n") else: haproxy_cfg.write(f" http-request set-header X-Forwarded-Proto http\n") - # Mode haproxy_cfg.write(f" mode {protocol}\n") - # ===== HOSTNAME ACL ===== + # ACL acl_name_sanitized = None if frontend_hostname: acl_name_sanitized = f"is_{sanitize_name(frontend_hostname)}" haproxy_cfg.write(f" acl {acl_name_sanitized} hdr(host) -i {frontend_hostname}\n") - # Balance settings for non-no-lb mode if not is_no_lb: haproxy_cfg.write(f" balance {lb_method}\n") if forward_for: haproxy_cfg.write(f" option forwardfor\n") - # DOS protection (BEFORE REDIRECT!) + # Protections if is_dos: haproxy_cfg.write(f" stick-table type ip size 1m expire {ban_duration} store http_req_rate(1m)\n") haproxy_cfg.write(f" http-request track-sc0 src\n") haproxy_cfg.write(f" acl abuse sc_http_req_rate(0) gt {limit_requests}\n") haproxy_cfg.write(f" http-request silent-drop if abuse\n") - # SQL Injection protection (BEFORE REDIRECT!) if sql_injection_check: haproxy_cfg.write(" acl is_sql_injection urlp_reg -i (union|select|insert|update|delete|drop|@@|1=1|`1)\n") haproxy_cfg.write(" acl is_long_uri path_len gt 400\n") @@ -156,63 +274,34 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, haproxy_cfg.write(" acl is_sql_injection2 urlp_reg -i (;|substring|extract|union\\s+all|order\\s+by)\\s+(\\d+|--\\+)\n") haproxy_cfg.write(f" http-request deny if is_sql_injection or is_long_uri or semicolon_path or is_sql_injection2\n") - # XSS protection (BEFORE REDIRECT!) if is_xss: haproxy_cfg.write(" acl is_xss_attack urlp_reg -i (<|>|script|alert|onerror|onload|javascript)\n") haproxy_cfg.write(" acl is_xss_attack_2 urlp_reg -i (<\\s*script\\s*|javascript:|<\\s*img\\s*src\\s*=|<\\s*a\\s*href\\s*=|<\\s*iframe\\s*src\\s*=|\\bon\\w+\\s*=|<\\s*input\\s*[^>]*\\s*value\\s*=|<\\s*form\\s*action\\s*=|<\\s*svg\\s*on\\w+\\s*=)\n") haproxy_cfg.write(" acl is_xss_attack_hdr hdr_reg(Cookie|Referer|User-Agent) -i (<|>|script|alert|onerror|onload|javascript)\n") haproxy_cfg.write(f" http-request deny if is_xss_attack or is_xss_attack_2 or is_xss_attack_hdr\n") - # Webshells protection (BEFORE REDIRECT!) if is_webshells: haproxy_cfg.write(" acl blocked_webshell path_reg -i /(cmd|shell|backdoor|webshell|phpspy|c99|kacak|b374k|log4j|log4shell|wsos|madspot|malicious|evil).*\\.php.*\n") haproxy_cfg.write(f" http-request deny if blocked_webshell\n") - # ===== CUSTOM ACL RULES (BEFORE REDIRECT!) ===== - if add_custom_acl and custom_acl_name and custom_acl_value: - # Write ACL rule based on type - if custom_acl_type == 'path_beg': - haproxy_cfg.write(f" acl {custom_acl_name} path_beg {custom_acl_value}\n") - elif custom_acl_type == 'path_end': - haproxy_cfg.write(f" acl {custom_acl_name} path_end {custom_acl_value}\n") - elif custom_acl_type == 'path_sub': - haproxy_cfg.write(f" acl {custom_acl_name} path_sub {custom_acl_value}\n") - elif custom_acl_type == 'hdr': - haproxy_cfg.write(f" acl {custom_acl_name} hdr_sub(host) -i {custom_acl_value}\n") - elif custom_acl_type == 'src': - haproxy_cfg.write(f" acl {custom_acl_name} src {custom_acl_value}\n") - elif custom_acl_type == 'method': - haproxy_cfg.write(f" acl {custom_acl_name} method {custom_acl_value}\n") - - # Apply action based on type - if custom_acl_action == 'deny': - haproxy_cfg.write(f" http-request deny if {custom_acl_name}\n") - elif custom_acl_action == 'redirect' and custom_acl_redirect_url: - haproxy_cfg.write(f" http-request redirect location {custom_acl_redirect_url} if {custom_acl_name}\n") - elif custom_acl_action == 'route' and custom_acl_backend: - haproxy_cfg.write(f" use_backend {custom_acl_backend} if {custom_acl_name}\n") - - # ===== HTTPS REDIRECT (AFTER ALL PROTECTIONS) ===== if https_redirect: haproxy_cfg.write(f" redirect scheme https code 301 if !{{ ssl_fc }}\n") - # ===== HTTP-RESPONSE RULES (AFTER REDIRECT) ===== if del_server_header: haproxy_cfg.write(f" http-response del-header Server\n") - # ===== BACKEND ROUTING ===== + # Backend routing if acl_name_sanitized: haproxy_cfg.write(f" use_backend {unique_backend_name} if {acl_name_sanitized}\n") else: haproxy_cfg.write(f" default_backend {unique_backend_name}\n") - # ===== PRIMARY BACKEND (WITH UNIQUE NAME) ===== + # ===== BACKEND ===== haproxy_cfg.write(f"\nbackend {unique_backend_name}\n") if not is_no_lb: haproxy_cfg.write(f" balance {lb_method}\n") - # Sticky sessions if sticky_session and not is_no_lb: if sticky_session_type == "cookie": haproxy_cfg.write(f" cookie SERVERID insert indirect nocache\n") @@ -220,17 +309,20 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, haproxy_cfg.write(f" stick-table type ip size 200k expire 30m\n") haproxy_cfg.write(f" stick on src\n") - # Health checks if health_check and protocol == 'http': haproxy_cfg.write(f" option httpchk GET {health_check_link}\n") elif health_check_tcp and protocol == 'tcp': haproxy_cfg.write(f" option tcp-check\n") - # Custom headers if add_header: haproxy_cfg.write(f" http-response add-header {header_name} {header_value}\n") - # Add backend servers + if del_server_header: + haproxy_cfg.write(f" http-response del-header Server\n") + + if forward_for: + haproxy_cfg.write(f" option forwardfor\n") + for server_name, server_ip, server_port, maxconn in backend_servers: maxconn_str = f" maxconn {maxconn}" if maxconn else "" @@ -239,27 +331,30 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method, else: haproxy_cfg.write(f" server {server_name} {server_ip}:{server_port}{maxconn_str}\n") - # ===== REDIRECT FRONTEND (HTTP -> HTTPS) ===== + # ===== REDIRECT HTTP -> HTTPS ===== if backend_ssl_redirect and ssl_redirect_backend_name: - unique_redirect_backend_name = f"{ssl_redirect_backend_name}_redirect_{sanitize_name(frontend_hostname)}" if frontend_hostname else f"{ssl_redirect_backend_name}_redirect" + unique_redirect_backend_name = f"{ssl_redirect_backend_name}_redirect_{sanitize_name(frontend_hostname)}" if frontend_hostname else ssl_redirect_backend_name - if is_backend_exist(unique_redirect_backend_name): - return f"Redirect backend {unique_redirect_backend_name} already exists. Cannot add duplicate." + # Check if HTTP frontend exists + existing_http_frontend = frontend_exists_at_port(frontend_ip, ssl_redirect_port) - # Generate unique name for redirect frontend - redirect_frontend_name = f"redirect_https_{sanitize_name(frontend_hostname)}" if frontend_hostname else f"redirect_https_{frontend_name}" - - haproxy_cfg.write(f"\nfrontend {redirect_frontend_name}\n") - haproxy_cfg.write(f" bind {frontend_ip}:{ssl_redirect_port}\n") - haproxy_cfg.write(f" mode http\n") - - # ===== HOSTNAME ACL FOR REDIRECT FRONTEND ===== - if frontend_hostname: - acl_name_redirect = f"is_{sanitize_name(frontend_hostname)}_redirect" - haproxy_cfg.write(f" acl {acl_name_redirect} hdr(host) -i {frontend_hostname}\n") - haproxy_cfg.write(f" use_backend {unique_redirect_backend_name} if {acl_name_redirect}\n") + if not existing_http_frontend: + redirect_frontend_name = f"redirect_https_{sanitize_name(frontend_hostname)}" if frontend_hostname else f"redirect_https_{frontend_name}" + + haproxy_cfg.write(f"\nfrontend {redirect_frontend_name}\n") + haproxy_cfg.write(f" bind {frontend_ip}:{ssl_redirect_port}\n") + haproxy_cfg.write(f" mode http\n") + + if frontend_hostname: + acl_name_redirect = f"is_{sanitize_name(frontend_hostname)}_redirect" + haproxy_cfg.write(f" acl {acl_name_redirect} hdr(host) -i {frontend_hostname}\n") + haproxy_cfg.write(f" use_backend {unique_redirect_backend_name} if {acl_name_redirect}\n") + else: + haproxy_cfg.write(f" default_backend {unique_redirect_backend_name}\n") else: - haproxy_cfg.write(f" default_backend {unique_redirect_backend_name}\n") + if frontend_hostname: + acl_name_redirect = f"is_{sanitize_name(frontend_hostname)}_redirect" + add_acl_to_frontend(existing_http_frontend, acl_name_redirect, frontend_hostname, unique_redirect_backend_name) # Redirect backend haproxy_cfg.write(f"\nbackend {unique_redirect_backend_name}\n")