From f96b426788df7e0cd4756a8b330da69f3b96d9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Mon, 3 Nov 2025 08:42:40 +0100 Subject: [PATCH] new options --- routes/main_routes.py | 10 ++- static/js/form.js | 24 ++++-- utils/haproxy_config.py | 184 +++++++++++++++++++++------------------- utils/stats_utils.py | 2 +- 4 files changed, 126 insertions(+), 94 deletions(-) diff --git a/routes/main_routes.py b/routes/main_routes.py index 017ac3a..6c88105 100644 --- a/routes/main_routes.py +++ b/routes/main_routes.py @@ -23,6 +23,11 @@ def index(): # Server header removal del_server_header = 'del_server_header' in request.form + # Backend SSL redirect + backend_ssl_redirect = 'backend_ssl_redirect' in request.form + ssl_redirect_backend_name = request.form.get('ssl_redirect_backend_name', '').strip() if backend_ssl_redirect else '' + ssl_redirect_port = request.form.get('ssl_redirect_port', '80') + # Backend servers backend_server_names = request.form.getlist('backend_server_names[]') backend_server_ips = request.form.getlist('backend_server_ips[]') @@ -148,7 +153,10 @@ def index(): root_redirect=root_redirect, redirect_to=redirect_to, is_webshells=is_webshells, - del_server_header=del_server_header + del_server_header=del_server_header, + backend_ssl_redirect=backend_ssl_redirect, + ssl_redirect_backend_name=ssl_redirect_backend_name, + ssl_redirect_port=ssl_redirect_port ) # Determine message type diff --git a/static/js/form.js b/static/js/form.js index e5e6443..fea9b55 100644 --- a/static/js/form.js +++ b/static/js/form.js @@ -101,6 +101,18 @@ bindToggle('#add_path_based', '#base_redirect_fields'); bindToggle('#add_acl_path', '#forbidden_fields'); + // Backend SSL redirect + const backendSslCheckbox = $('#backend_ssl_redirect'); + const backendSslFields = $('#backend_ssl_fields'); + + backendSslCheckbox?.addEventListener('change', function() { + toggle(this.checked, backendSslFields); + if (this.checked) { + // Show frontend port field + document.getElementById('ssl_redirect_port')?.parentElement.classList.remove('d-none'); + } + }); + // LB Method - obsługa trybu no-lb const lbMethodSelect = $('#lb_method'); const backendServersContainer = $('#backend_servers_container'); @@ -110,29 +122,29 @@ const isNoLb = lbMethodSelect?.value === 'no-lb'; if (isNoLb) { - // Ukryj przycisk dodawania kolejnych serwerów + // Hide add server button if (addServerBtn) addServerBtn.classList.add('d-none'); - // Zostaw tylko pierwszy serwer i usuń pozostałe + // Keep only first server and remove others const serverRows = $$('.backend-server-row', backendServersContainer); serverRows.forEach((row, idx) => { if (idx > 0) row.remove(); }); - // Dodaj informację o trybie no-lb jeśli jeszcze nie istnieje + // Add info about no-lb mode if it doesn't exist if (!$('.no-lb-info')) { const info = document.createElement('div'); info.className = 'alert alert-info alert-sm no-lb-info mt-2'; - info.innerHTML = 'Tryb no-lb: frontend → backend → pojedynczy serwer. Możesz włączyć XSS, DOS, SQL injection protection itp.'; + info.innerHTML = 'Mode no-lb: frontend → backend → single server. You can still enable XSS, DOS, SQL injection protection etc.'; if (backendServersContainer?.parentElement) { backendServersContainer.parentElement.appendChild(info); } } } else { - // Pokaż przycisk dodawania serwerów + // Show add server button if (addServerBtn) addServerBtn.classList.remove('d-none'); - // Usuń informację o no-lb + // Remove no-lb info const info = $('.no-lb-info'); if (info) info.remove(); } diff --git a/utils/haproxy_config.py b/utils/haproxy_config.py index 9b054c6..3624593 100644 --- a/utils/haproxy_config.py +++ b/utils/haproxy_config.py @@ -5,7 +5,7 @@ HAPROXY_CFG = '/etc/haproxy/haproxy.cfg' def is_frontend_exist(frontend_name, frontend_ip, frontend_port): if not os.path.exists(HAPROXY_CFG): return False - + try: with open(HAPROXY_CFG, 'r') as haproxy_cfg: frontend_found = False @@ -23,13 +23,13 @@ def is_frontend_exist(frontend_name, frontend_ip, frontend_port): return True except Exception as e: print(f"[HAPROXY_CONFIG] Error checking frontend: {e}", flush=True) - + return False def is_backend_exist(backend_name): if not os.path.exists(HAPROXY_CFG): return False - + try: with open(HAPROXY_CFG, 'r') as haproxy_cfg: for line in haproxy_cfg: @@ -40,69 +40,24 @@ def is_backend_exist(backend_name): return True except Exception as e: print(f"[HAPROXY_CONFIG] Error checking backend: {e}", flush=True) - + return False -def update_simple_haproxy_config(frontend_name, frontend_host, use_ssl, ssl_cert_path, - backend_name, backend_ip, backend_port, - forward_for=True, del_server_header=True): - """ - Tworzy prostą konfigurację frontend->backend bez load balancingu - """ - os.makedirs(os.path.dirname(HAPROXY_CFG), exist_ok=True) - - if is_backend_exist(backend_name): - return f"Backend {backend_name} already exists. Cannot add duplicate." - - try: - with open(HAPROXY_CFG, 'a') as haproxy_cfg: - # Frontend section - haproxy_cfg.write(f"\nfrontend {frontend_name}\n") - - if use_ssl: - haproxy_cfg.write(f" bind :443 ssl crt {ssl_cert_path}\n") - else: - haproxy_cfg.write(f" bind :80\n") - - # Headers - if forward_for: - haproxy_cfg.write(f" http-request set-header X-Forwarded-For %[src]\n") - haproxy_cfg.write(f" http-request set-header X-Forwarded-Proto {'https' if use_ssl else 'http'}\n") - - if del_server_header: - haproxy_cfg.write(f" http-response del-header Server\n") - - # ACL dla hosta - haproxy_cfg.write(f"\n acl host_{backend_name} hdr(host) -i {frontend_host}\n") - haproxy_cfg.write(f" use_backend {backend_name} if host_{backend_name}\n") - - # Backend section - haproxy_cfg.write(f"\nbackend {backend_name}\n") - haproxy_cfg.write(f" server s1 {backend_ip}:{backend_port} check\n") - - return "Configuration updated successfully!" - - except Exception as e: - print(f"[HAPROXY_CONFIG] Error updating simple config: {e}", flush=True) - return f"Error: {e}" - - - def count_frontends_and_backends(): if not os.path.exists(HAPROXY_CFG): return 0, 0, 0, 0, 0 - + frontend_count = 0 backend_count = 0 acl_count = 0 layer7_count = 0 layer4_count = 0 - + try: with open(HAPROXY_CFG, 'r') as haproxy_cfg: content = haproxy_cfg.read() lines = content.split('\n') - + for line in lines: line_stripped = line.strip() if line_stripped.startswith('frontend'): @@ -117,97 +72,154 @@ def count_frontends_and_backends(): acl_count += 1 except Exception as e: print(f"[HAPROXY_CONFIG] Error counting: {e}", flush=True) - + return frontend_count, backend_count, acl_count, layer7_count, layer4_count -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, - acl_action, acl_backend_name, use_ssl, ssl_cert_path, https_redirect, is_dos, - ban_duration, limit_requests, forward_for, is_forbidden_path, forbidden_name, - allowed_ip, forbidden_path, sql_injection_check, is_xss, is_remote_upload, - add_path_based, redirect_domain_name, root_redirect, redirect_to, is_webshells): - +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, + acl_action, acl_backend_name, use_ssl, ssl_cert_path, https_redirect, is_dos, + ban_duration, limit_requests, forward_for, is_forbidden_path, forbidden_name, + allowed_ip, forbidden_path, sql_injection_check, is_xss, is_remote_upload, + add_path_based, redirect_domain_name, root_redirect, redirect_to, is_webshells, + del_server_header=False, backend_ssl_redirect=False, ssl_redirect_backend_name='', + ssl_redirect_port='80'): + os.makedirs(os.path.dirname(HAPROXY_CFG), exist_ok=True) - + if is_backend_exist(backend_name): return f"Backend {backend_name} already exists. Cannot add duplicate." - + + # Tryb no-lb - prosty frontend→backend z pojedynczym serwerem + is_no_lb = lb_method == 'no-lb' + if is_no_lb and len(backend_servers) > 1: + backend_servers = backend_servers[:1] # Tylko pierwszy serwer + try: with open(HAPROXY_CFG, 'a') as haproxy_cfg: 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: haproxy_cfg.write(f" ssl crt {ssl_cert_path}") + haproxy_cfg.write("\n") - + + # HTTPS redirect if https_redirect: haproxy_cfg.write(f" redirect scheme https code 301 if !{{ ssl_fc }}\n") - - if forward_for: - haproxy_cfg.write(f" option forwardfor\n") - + + # Ustaw mode - zawsze dla no-lb (nie ma balance) haproxy_cfg.write(f" mode {protocol}\n") - haproxy_cfg.write(f" balance {lb_method}\n") - + + # W trybie no-lb używamy prostych nagłówków HTTP-request + if is_no_lb: + 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") + + # Opcja ukrycia nagłówka Server + if del_server_header: + haproxy_cfg.write(f" http-response del-header Server\n") + else: + # Standardowy tryb z option forwardfor i balance + haproxy_cfg.write(f" balance {lb_method}\n") + + if forward_for: + haproxy_cfg.write(f" option forwardfor\n") + + if del_server_header: + haproxy_cfg.write(f" http-response del-header Server\n") + + # DOS protection - działa w obu trybach 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 - działa w obu trybach 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") haproxy_cfg.write(" acl semicolon_path path_reg -i ^.*;.*\n") 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 - działa w obu trybach 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 - działa w obu trybach 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") - + + # Default backend haproxy_cfg.write(f" default_backend {backend_name}\n") - + # Backend section haproxy_cfg.write(f"\nbackend {backend_name}\n") - haproxy_cfg.write(f" balance {lb_method}\n") - - if sticky_session: + + # Balance tylko dla standardowego trybu + if not is_no_lb: + haproxy_cfg.write(f" balance {lb_method}\n") + + # Sticky sessions - tylko dla standardowego trybu + 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") - + + # Health checks - działa w obu trybach 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 - działa w obu trybach if add_header: haproxy_cfg.write(f" http-response add-header {header_name} {header_value}\n") - + # Add backend servers 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") - - return "Configuration updated successfully!" + + # ========== REDIRECT FRONTEND (HTTP -> HTTPS) ========== + if backend_ssl_redirect and ssl_redirect_backend_name: + # Sprawdź czy taki backend już istnieje + if is_backend_exist(ssl_redirect_backend_name): + return f"Redirect backend {ssl_redirect_backend_name} already exists. Cannot add duplicate." + + haproxy_cfg.write(f"\nfrontend redirect_https\n") + haproxy_cfg.write(f" bind {frontend_ip}:{ssl_redirect_port}\n") + haproxy_cfg.write(f" mode http\n") + haproxy_cfg.write(f" default_backend {ssl_redirect_backend_name}\n") + + # Redirect backend + haproxy_cfg.write(f"\nbackend {ssl_redirect_backend_name}\n") + haproxy_cfg.write(f" mode http\n") + haproxy_cfg.write(f" redirect scheme https code 301 if !{{ ssl_fc }}\n") + + return "Configuration updated successfully!" + except Exception as e: print(f"[HAPROXY_CONFIG] Error updating config: {e}", flush=True) return f"Error: {e}" diff --git a/utils/stats_utils.py b/utils/stats_utils.py index 38c9f47..503baf7 100644 --- a/utils/stats_utils.py +++ b/utils/stats_utils.py @@ -1,7 +1,7 @@ import requests import csv -HAPROXY_STATS_URL = 'http://127.0.0.1:8404/;csv' +HAPROXY_STATS_URL = 'http://127.0.0.1:8404/stats;csv' def fetch_haproxy_stats(): try: