diff --git a/routes/edit_routes.py b/routes/edit_routes.py index b05024d..bcd385b 100644 --- a/routes/edit_routes.py +++ b/routes/edit_routes.py @@ -32,7 +32,7 @@ def edit_haproxy_config(): if result.returncode == 0: if not out: - out = "Configuration file is valid ✅" + out = "Configuration file is valid" level = "success" if "Warning" in out or "Warnings" in out: level = "warning" diff --git a/routes/main_routes.py b/routes/main_routes.py index 95a5436..017ac3a 100644 --- a/routes/main_routes.py +++ b/routes/main_routes.py @@ -1,11 +1,9 @@ from flask import Blueprint, render_template, request -from auth.auth_middleware import requires_auth # Updated import +from auth.auth_middleware import requires_auth from utils.haproxy_config import update_haproxy_config, is_frontend_exist, count_frontends_and_backends main_bp = Blueprint('main', __name__) - - @main_bp.route('/', methods=['GET', 'POST']) @requires_auth def index(): @@ -16,89 +14,156 @@ def index(): lb_method = request.form['lb_method'] protocol = request.form['protocol'] backend_name = request.form['backend_name'] + + # Header options add_header = 'add_header' in request.form header_name = request.form.get('header_name', '') if add_header else '' header_value = request.form.get('header_value', '') if add_header else '' - - # Get all backend servers data + # Server header removal + del_server_header = 'del_server_header' in request.form + + # Backend servers backend_server_names = request.form.getlist('backend_server_names[]') backend_server_ips = request.form.getlist('backend_server_ips[]') backend_server_ports = request.form.getlist('backend_server_ports[]') backend_server_maxconns = request.form.getlist('backend_server_maxconns[]') - + + # ACL is_acl = 'add_acl' in request.form - acl_name = request.form['acl'] if 'acl' in request.form else '' - acl_action = request.form['acl_action'] if 'acl_action' in request.form else '' - acl_backend_name = request.form['backend_name_acl'] if 'backend_name_acl' in request.form else '' + acl_name = request.form.get('acl', '') + acl_action = request.form.get('acl_action', '') + acl_backend_name = request.form.get('backend_name_acl', '') + + # SSL use_ssl = 'ssl_checkbox' in request.form - ssl_cert_path = request.form['ssl_cert_path'] + ssl_cert_path = request.form.get('ssl_cert_path', '/etc/haproxy/certs/haproxy.pem') https_redirect = 'ssl_redirect_checkbox' in request.form - is_dos = 'add_dos' in request.form if 'add_dos' in request.form else '' - ban_duration = request.form["ban_duration"] - limit_requests = request.form["limit_requests"] + + # DOS Protection + is_dos = 'add_dos' in request.form + ban_duration = request.form.get('ban_duration', '30m') + limit_requests = request.form.get('limit_requests', '100') + + # Forward For forward_for = 'forward_for_check' in request.form - + + # Forbidden paths is_forbidden_path = 'add_acl_path' in request.form - forbidden_name = request.form["forbidden_name"] - allowed_ip = request.form["allowed_ip"] - forbidden_path = request.form["forbidden_path"] - - sql_injection_check = 'sql_injection_check' in request.form if 'sql_injection_check' in request.form else '' - is_xss = 'xss_check' in request.form if 'xss_check' in request.form else '' - is_remote_upload = 'remote_uploads_check' in request.form if 'remote_uploads_check' in request.form else '' - + forbidden_name = request.form.get('forbidden_name', '') + allowed_ip = request.form.get('allowed_ip', '') + forbidden_path = request.form.get('forbidden_path', '') + + # SQL Injection + sql_injection_check = 'sql_injection_check' in request.form + + # XSS + is_xss = 'xss_check' in request.form + + # Remote uploads + is_remote_upload = 'remote_uploads_check' in request.form + + # Path-based redirects add_path_based = 'add_path_based' in request.form - redirect_domain_name = request.form["redirect_domain_name"] - root_redirect = request.form["root_redirect"] - redirect_to = request.form["redirect_to"] - is_webshells = 'webshells_check' in request.form if 'webshells_check' in request.form else '' - - # Combine backend server info into a list of tuples (name, ip, port, maxconns) + redirect_domain_name = request.form.get('redirect_domain_name', '') + root_redirect = request.form.get('root_redirect', '') + redirect_to = request.form.get('redirect_to', '') + + # Webshells + is_webshells = 'webshells_check' in request.form + + # Build backend_servers list backend_servers = [] for i in range(len(backend_server_ips)): name = backend_server_names[i] if i < len(backend_server_names) else f"server{i+1}" ip = backend_server_ips[i] if i < len(backend_server_ips) else '' port = backend_server_ports[i] if i < len(backend_server_ports) else '' maxconn = backend_server_maxconns[i] if i < len(backend_server_maxconns) else None - - if ip and port: # Only add if we have IP and port + + if ip and port: backend_servers.append((name, ip, port, maxconn)) - # Check if frontend or port already exists + # Validate frontend existence if is_frontend_exist(frontend_name, frontend_ip, frontend_port): - return render_template('index.html', message="Frontend or Port already exists. Cannot add duplicate.") - - # Get health check related fields if the protocol is HTTP + return render_template('index.html', + message="Frontend or Port already exists. Cannot add duplicate.", + message_type="danger") + + # Health checks health_check = False health_check_link = "" if protocol == 'http': health_check = 'health_check' in request.form if health_check: - health_check_link = request.form['health_check_link'] - + health_check_link = request.form.get('health_check_link', '/') + health_check_tcp = False if protocol == 'tcp': health_check_tcp = 'health_check2' in request.form - - # Get sticky session related fields + + # Sticky session sticky_session = False sticky_session_type = "" if 'sticky_session' in request.form: sticky_session = True - sticky_session_type = request.form['sticky_session_type'] - - # Update the HAProxy config file + sticky_session_type = request.form.get('sticky_session_type', 'cookie') + + # Call update_haproxy_config with all parameters message = 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 + frontend_name=frontend_name, + frontend_ip=frontend_ip, + frontend_port=frontend_port, + lb_method=lb_method, + protocol=protocol, + backend_name=backend_name, + backend_servers=backend_servers, + health_check=health_check, + health_check_tcp=health_check_tcp, + health_check_link=health_check_link, + sticky_session=sticky_session, + add_header=add_header, + header_name=header_name, + header_value=header_value, + sticky_session_type=sticky_session_type, + is_acl=is_acl, + acl_name=acl_name, + acl_action=acl_action, + acl_backend_name=acl_backend_name, + use_ssl=use_ssl, + ssl_cert_path=ssl_cert_path, + https_redirect=https_redirect, + is_dos=is_dos, + ban_duration=ban_duration, + limit_requests=limit_requests, + forward_for=forward_for, + is_forbidden_path=is_forbidden_path, + forbidden_name=forbidden_name, + allowed_ip=allowed_ip, + forbidden_path=forbidden_path, + sql_injection_check=sql_injection_check, + is_xss=is_xss, + is_remote_upload=is_remote_upload, + add_path_based=add_path_based, + redirect_domain_name=redirect_domain_name, + root_redirect=root_redirect, + redirect_to=redirect_to, + is_webshells=is_webshells, + del_server_header=del_server_header ) - return render_template('index.html', message=message) - - return render_template('index.html') - + + # Determine message type + message_type = "success" if "successfully" in message else "danger" + + return render_template('index.html', + message=message, + message_type=message_type) + + # GET request - display stats + frontend_count, backend_count, acl_count, layer7_count, layer4_count = count_frontends_and_backends() + + return render_template('index.html', + frontend_count=frontend_count, + backend_count=backend_count, + acl_count=acl_count, + layer7_count=layer7_count, + layer4_count=layer4_count) diff --git a/static/js/form.js b/static/js/form.js new file mode 100644 index 0000000..e5e6443 --- /dev/null +++ b/static/js/form.js @@ -0,0 +1,191 @@ +(() => { + 'use strict'; + + // Toggle health check fields based on protocol + const protocolSelect = document.getElementById('protocol'); + const healthCheckFields = document.getElementById('health_check_fields'); + const tcpHealthCheck = document.getElementById('tcp_health_check'); + + const onProtocolChange = () => { + if (protocolSelect?.value === 'http') { + document.getElementById('health_check')?.parentElement.parentElement.style.display = 'block'; + tcpHealthCheck.style.display = 'none'; + } else { + document.getElementById('health_check')?.parentElement.parentElement.style.display = 'none'; + tcpHealthCheck.style.display = 'flex'; + } + }; + + protocolSelect?.addEventListener('change', onProtocolChange); + + // Toggle sticky session fields + const stickyCheckbox = document.getElementById('sticky_session'); + const stickyFields = document.getElementById('sticky_fields'); + + stickyCheckbox?.addEventListener('change', function() { + stickyFields.classList.toggle('d-none', !this.checked); + }); + + // Toggle health check link field + const healthCheckbox = document.getElementById('health_check'); + healthCheckbox?.addEventListener('change', function() { + document.getElementById('health_check_fields')?.classList.toggle('d-none', !this.checked); + }); + + // Toggle header fields + const headerCheckbox = document.getElementById('add_header'); + const headerFields = document.querySelectorAll('#header_fields'); + + headerCheckbox?.addEventListener('change', function() { + headerFields.forEach(field => field.classList.toggle('d-none', !this.checked)); + }); + + // Helper functions + const $ = (sel, root = document) => root.querySelector(sel); + const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); + const toggle = (on, el) => el.classList.toggle('d-none', !on); + + // SSL fields + const sslCheckbox = $('#ssl_checkbox'); + const sslFields = $('#ssl_fields'); + sslCheckbox?.addEventListener('change', () => toggle(sslCheckbox.checked, sslFields)); + + // DOS + const dosCheckbox = $('#add_dos'); + const dosFields = $('#dos_fields'); + dosCheckbox?.addEventListener('change', () => toggle(dosCheckbox.checked, dosFields)); + + // HTTP only groups + const httpGroups = $$('.http-only, #forbidden_acl_container'); + const httpToggles = [ + $('#sql_injection_check'), + $('#xss_check'), + $('#remote_uploads_check'), + $('#webshells_check'), + $('#forward_for_check'), + $('#add_acl_path'), + $('#add_path_based'), + ]; + + const forbiddenFields = $('#forbidden_fields'); + const pathFields = $('#base_redirect_fields'); + + const onProtocolChangeExtended = () => { + const isHttp = protocolSelect?.value === 'http'; + httpGroups.forEach(el => toggle(isHttp, el)); + + if (!isHttp) { + [forbiddenFields, pathFields].forEach(el => el && toggle(false, el)); + httpToggles.forEach(input => { + if (input) input.checked = false; + }); + } + }; + + protocolSelect?.addEventListener('change', onProtocolChangeExtended); + onProtocolChangeExtended(); + + // ACL + const aclCheckbox = $('#add_acl'); + const aclFields = $('#acl_fields'); + aclCheckbox?.addEventListener('change', () => toggle(aclCheckbox.checked, aclFields)); + + // toggles that reveal their fields + const bindToggle = (checkboxSel, targetSel) => { + const cb = $(checkboxSel); + const target = $(targetSel); + cb?.addEventListener('change', () => toggle(cb.checked, target)); + if (cb && target) toggle(cb.checked, target); + }; + + bindToggle('#add_path_based', '#base_redirect_fields'); + bindToggle('#add_acl_path', '#forbidden_fields'); + + // LB Method - obsługa trybu no-lb + const lbMethodSelect = $('#lb_method'); + const backendServersContainer = $('#backend_servers_container'); + const addServerBtn = $('#add_backend_btn'); + + const onLbMethodChange = () => { + const isNoLb = lbMethodSelect?.value === 'no-lb'; + + if (isNoLb) { + // Ukryj przycisk dodawania kolejnych serwerów + if (addServerBtn) addServerBtn.classList.add('d-none'); + + // Zostaw tylko pierwszy serwer i usuń pozostałe + 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 + 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.'; + if (backendServersContainer?.parentElement) { + backendServersContainer.parentElement.appendChild(info); + } + } + } else { + // Pokaż przycisk dodawania serwerów + if (addServerBtn) addServerBtn.classList.remove('d-none'); + + // Usuń informację o no-lb + const info = $('.no-lb-info'); + if (info) info.remove(); + } + }; + + lbMethodSelect?.addEventListener('change', onLbMethodChange); + if (lbMethodSelect) onLbMethodChange(); + + // Backend rows + let serverCount = 1; + const container = $('#backend_servers_container'); + const addBtn = $('#add_backend_btn'); + + const createRow = () => { + serverCount++; + const row = document.createElement('div'); + row.className = 'row g-3 backend-server-row mt-1'; + row.innerHTML = ` +