new options

This commit is contained in:
Mateusz Gruszczyński
2025-11-03 12:17:32 +01:00
parent c7a09171e1
commit 2d53843f34
3 changed files with 120 additions and 161 deletions

View File

@@ -1,12 +1,10 @@
from flask import Blueprint, render_template, request, flash
from flask import Blueprint, render_template, request
import subprocess
from auth.auth_middleware import requires_auth
from utils.haproxy_config import update_haproxy_config, is_frontend_exist, count_frontends_and_backends
from utils.haproxy_config import update_haproxy_config, count_frontends_and_backends
main_bp = Blueprint('main', __name__)
import subprocess
def reload_haproxy():
"""Reload HAProxy by killing it - supervisord restarts automatically"""
try:
@@ -37,20 +35,19 @@ def reload_haproxy():
else:
print(f"[HAPROXY] pkill failed: {result.stdout}", flush=True)
return False, f"pkill failed: {result.stdout}"
except Exception as e:
print(f"[HAPROXY] Error: {e}", flush=True)
return False, f"Error: {str(e)}"
@main_bp.route('/', methods=['GET', 'POST'])
@requires_auth
def index():
if request.method == 'POST':
frontend_name = request.form['frontend_name']
# Frontend IP i port (używane do generowania nazwy frontendu)
frontend_ip = request.form['frontend_ip']
frontend_port = request.form['frontend_port']
frontend_hostname = request.form.get('frontend_hostname', '').strip()
lb_method = request.form['lb_method']
protocol = request.form['protocol']
backend_name = request.form['backend_name']
@@ -130,12 +127,6 @@ def index():
if ip and port:
backend_servers.append((name, ip, port, maxconn))
# 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.",
message_type="danger")
# Health checks
health_check = False
health_check_link = ""
@@ -161,6 +152,10 @@ def index():
acl_action = ''
acl_backend_name = ''
# ===== GENERUJ FRONTEND NAME ZAMIAST PRZYJMOWAĆ OD USERA =====
# frontend_name będzie generowany w haproxy_config.py
frontend_name = None # Będzie wygenerowany automatycznie
# Call update_haproxy_config
message = update_haproxy_config(
frontend_name=frontend_name,

View File

@@ -13,7 +13,6 @@
</nav>
{% endblock %}
{% block content %}
<div class="row mb-4">
@@ -67,32 +66,24 @@
<div class="card-body">
<!-- FRONTEND SECTION -->
<h6 class="text-primary mb-3"><i class="bi bi-hdd-network me-2"></i>Frontend</h6>
<h6 class="text-primary mb-3"><i class="bi bi-hdd-network me-2"></i>Frontend Configuration</h6>
<div class="row g-3 mb-3">
<div class="col-md-4">
<label for="frontend_name" class="form-label">Frontend Name</label>
<input type="text" class="form-control" id="frontend_name" name="frontend_name"
placeholder="e.g. fe_web" required>
</div>
<div class="col-md-4">
<label for="frontend_ip" class="form-label">IP Address (bind)</label>
<label for="frontend_ip" class="form-label">Listener IP</label>
<input type="text" class="form-control" id="frontend_ip" name="frontend_ip"
placeholder="0.0.0.0" value="0.0.0.0" required>
</div>
<div class="col-md-4">
<label for="frontend_port" class="form-label">Port</label>
<label for="frontend_port" class="form-label">Listener Port</label>
<input type="number" class="form-control" id="frontend_port" name="frontend_port"
placeholder="443" value="443" min="1" max="65535" required>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-12">
<label for="frontend_hostname" class="form-label">Frontend Hostname (Domain)</label>
<div class="col-md-4">
<label for="frontend_hostname" class="form-label">Frontend Hostname</label>
<input type="text" class="form-control" id="frontend_hostname" name="frontend_hostname"
placeholder="e.g. host.domain.com" required>
<div class="form-text">Domain name for the ACL rule - traffic will be matched by Host header</div>
<small class="text-muted d-block mt-1">Frontend name will be generated automatically</small>
</div>
</div>
@@ -114,7 +105,6 @@
<option value="static-rr">Static Round Robin (WRR)</option>
<option value="no-lb">No Load Balancing (single host)</option>
</select>
<div class="form-text">Choose load balancing algorithm or simple single host</div>
</div>
</div>
@@ -135,7 +125,7 @@
<label for="ssl_cert_path" class="form-label">SSL Certificate Path</label>
<input type="text" class="form-control" id="ssl_cert_path" name="ssl_cert_path"
value="/app/ssl/haproxy-configurator.pem">
<div class="form-text">Full path to .pem file, upload certs in /ssl/</div>
<small class="text-muted">Full path to .pem file</small>
</div>
<div class="col-md-12">
<div class="form-check">
@@ -157,24 +147,23 @@
<label class="form-check-label" for="backend_ssl_redirect">
<i class="bi bi-arrow-repeat me-1"></i>Add HTTP Redirect to HTTPS
</label>
<div class="form-text small">Creates additional frontend on port 80 to redirect HTTP to HTTPS backend</div>
<small class="text-muted d-block">Creates additional frontend on port 80</small>
</div>
</div>
</div>
<div class="row g-3 mb-3 d-none" id="backend_ssl_fields">
<div class="col-md-12">
<label for="ssl_redirect_backend_name" class="form-label">Redirect Backend Name (HTTP → HTTPS)</label>
<label for="ssl_redirect_backend_name" class="form-label">Redirect Backend Name</label>
<input type="text" class="form-control" id="ssl_redirect_backend_name"
name="ssl_redirect_backend_name" placeholder="e.g. be_redirect">
<div class="form-text">Name for the redirect backend that will push traffic to HTTPS</div>
name="ssl_redirect_backend_name" placeholder="e.g. redirect">
</div>
</div>
<hr class="my-4">
<!-- BACKEND SECTION -->
<h6 class="text-primary mb-3"><i class="bi bi-hdd-rack me-2"></i>Backend</h6>
<h6 class="text-primary mb-3"><i class="bi bi-hdd-rack me-2"></i>Backend Configuration</h6>
<div class="row g-3 mb-3">
<div class="col-md-12">
@@ -297,9 +286,7 @@
<label class="form-check-label" for="del_server_header">
<i class="bi bi-shield-lock me-1"></i>Hide Server Header
</label>
<div class="form-text small">
Adds: <code>http-response del-header Server</code> (security)
</div>
<small class="text-muted d-block">Adds: <code>http-response del-header Server</code></small>
</div>
</div>
</div>
@@ -336,7 +323,7 @@
<label for="ban_duration" class="form-label">Ban Duration</label>
<input type="text" class="form-control" id="ban_duration" name="ban_duration"
value="30m" placeholder="30m">
<div class="form-text">e.g. 30m, 1h, 24h</div>
<small class="text-muted">e.g. 30m, 1h, 24h</small>
</div>
<div class="col-md-6 d-none" id="dos_fields">
<label for="limit_requests" class="form-label">Request Limit (per min)</label>
@@ -398,7 +385,7 @@
<hr class="my-4">
<!-- CUSTOM ACL SECTION (NEW) -->
<!-- CUSTOM ACL SECTION -->
<h6 class="text-primary mb-3"><i class="bi bi-shuffle me-2"></i>Custom ACL Rules (Advanced)</h6>
<div class="row g-3 mb-3 http-only">
@@ -408,18 +395,17 @@
<label class="form-check-label" for="add_custom_acl">
<i class="bi bi-sliders me-1"></i>Add Custom ACL Rule
</label>
<div class="form-text small">Create additional routing or blocking rules beyond hostname matching</div>
<small class="text-muted d-block">Create additional routing or blocking rules</small>
</div>
</div>
</div>
<!-- Custom ACL Fields (Hidden by default) -->
<!-- Custom ACL Fields -->
<div class="row g-3 mb-3 http-only d-none" id="custom_acl_fields">
<div class="col-md-3">
<label for="custom_acl_name" class="form-label">ACL Name</label>
<input type="text" class="form-control" id="custom_acl_name" name="custom_acl_name"
placeholder="e.g. is_admin_path">
<div class="form-text small">Unique identifier for this rule</div>
</div>
<div class="col-md-3">
@@ -437,8 +423,7 @@
<div class="col-md-3">
<label for="custom_acl_value" class="form-label">Rule Value</label>
<input type="text" class="form-control" id="custom_acl_value" name="custom_acl_value"
placeholder="e.g. /admin, api, 192.168.1.0/24, GET">
<div class="form-text small">Value to match (path, header name, IP, method)</div>
placeholder="e.g. /admin, api, 192.168.1.0/24">
</div>
<div class="col-md-3">
@@ -454,14 +439,12 @@
<label for="custom_acl_backend" class="form-label">Target Backend</label>
<input type="text" class="form-control" id="custom_acl_backend" name="custom_acl_backend"
placeholder="e.g. be_admin">
<div class="form-text small">Backend to send matching traffic to</div>
</div>
<div class="col-md-6 d-none" id="acl_redirect_select">
<label for="custom_acl_redirect_url" class="form-label">Redirect URL</label>
<input type="text" class="form-control" id="custom_acl_redirect_url" name="custom_acl_redirect_url"
placeholder="e.g. https://example.com/new-path">
<div class="form-text small">URL to redirect matching requests to</div>
</div>
</div>

View File

@@ -2,29 +2,84 @@ import os
HAPROXY_CFG = '/etc/haproxy/haproxy.cfg'
def is_frontend_exist(frontend_name, frontend_ip, frontend_port):
def sanitize_name(name):
"""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'):
# Szukaj bind line
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 line.strip().split(' ', 1)[1] # Zwróć nazwę frontendu
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 haproxy_cfg:
frontend_found = False
for line in haproxy_cfg:
if line.strip().startswith('frontend'):
_, existing_frontend_name = line.strip().split(' ', 1)
if existing_frontend_name.strip() == frontend_name:
frontend_found = True
else:
frontend_found = False
elif frontend_found and line.strip().startswith('bind'):
_, bind_info = line.strip().split(' ', 1)
existing_ip, existing_port = bind_info.split(':', 1)
if existing_ip.strip() == frontend_ip and existing_port.strip() == frontend_port:
return True
with open(HAPROXY_CFG, 'r') as f:
lines = f.readlines()
# Znajdź frontend
frontend_idx = -1
for i, line in enumerate(lines):
if 'frontend' in line and frontend_name in line:
frontend_idx = i
break
if frontend_idx == -1:
return False
# Sprawdź czy ACL już istnieje
for line in lines[frontend_idx:]:
if acl_name in line and 'acl' in line:
return True # Już istnieje
if line.strip().startswith('backend'):
break
# Znajdź ostatnią linię ACL/use_backend w tym frontendzie
insert_idx = frontend_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 + 1
# Wstaw ACL i use_backend
acl_line = f" acl {acl_name} hdr(host) -i {hostname}\n"
use_backend_line = f" use_backend {backend_name} if {acl_name}\n"
lines.insert(insert_idx, use_backend_line)
lines.insert(insert_idx, acl_line)
with open(HAPROXY_CFG, 'w') as f:
f.writelines(lines)
return True
except Exception as e:
print(f"[HAPROXY_CONFIG] Error checking frontend: {e}", flush=True)
return False
print(f"[HAPROXY_CONFIG] Error adding ACL: {e}", flush=True)
return False
def is_backend_exist(backend_name):
if not os.path.exists(HAPROXY_CFG):
@@ -75,85 +130,6 @@ 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"""
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,
@@ -182,11 +158,11 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method,
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
# Frontend już istnieje - dodaj tylko backend + ACL
print(f"[HAPROXY] Found existing frontend '{existing_frontend}' at {frontend_ip}:{frontend_port}", flush=True)
# Utwórz backend
with open(HAPROXY_CFG, 'a') as haproxy_cfg:
# ===== BACKEND =====
haproxy_cfg.write(f"\nbackend {unique_backend_name}\n")
if not is_no_lb:
@@ -213,6 +189,7 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method,
if forward_for:
haproxy_cfg.write(f" option forwardfor\n")
# Add servers
for server_name, server_ip, server_port, maxconn in backend_servers:
maxconn_str = f" maxconn {maxconn}" if maxconn else ""
@@ -221,18 +198,22 @@ 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")
# Dodaj ACL do istniejącego frontendu
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}"
return f"Backend added to existing frontend"
# ===== TWORZENIE NOWEGO FRONTENDU (GENERYCZNE NAZWY) =====
# Generuj generyczną nazwę frontendu
generic_frontend_name = f"https_frontend" if use_ssl else f"http_frontend"
generic_http_redirect_name = f"http_redirect_frontend"
print(f"[HAPROXY] Creating new frontend '{generic_frontend_name}' at {frontend_ip}:{frontend_port}", flush=True)
# ===== TWORZENIE NOWEGO FRONTENDU =====
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."
# ===== PRIMARY FRONTEND (GENERIC NAME) =====
haproxy_cfg.write(f"\nfrontend {generic_frontend_name}\n")
haproxy_cfg.write(f" bind {frontend_ip}:{frontend_port}")
if use_ssl:
@@ -240,7 +221,7 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method,
haproxy_cfg.write("\n")
# Headers ZARAZ PO 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")
@@ -249,7 +230,7 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method,
haproxy_cfg.write(f" mode {protocol}\n")
# ACL
# ACL dla pierwszego vhost
acl_name_sanitized = None
if frontend_hostname:
acl_name_sanitized = f"is_{sanitize_name(frontend_hostname)}"
@@ -331,17 +312,16 @@ 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 HTTP -> HTTPS =====
# ===== REDIRECT HTTP -> HTTPS (GENERIC NAME) =====
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 ssl_redirect_backend_name
# Check if HTTP frontend exists
# Check if HTTP redirect frontend exists
existing_http_frontend = frontend_exists_at_port(frontend_ip, ssl_redirect_port)
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")
# Utwórz nowy HTTP redirect frontend (generic name)
haproxy_cfg.write(f"\nfrontend {generic_http_redirect_name}\n")
haproxy_cfg.write(f" bind {frontend_ip}:{ssl_redirect_port}\n")
haproxy_cfg.write(f" mode http\n")
@@ -352,6 +332,7 @@ def update_haproxy_config(frontend_name, frontend_ip, frontend_port, lb_method,
else:
haproxy_cfg.write(f" default_backend {unique_redirect_backend_name}\n")
else:
# Dodaj ACL do istniejącego HTTP frontendu
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)