fix(ssl): comprehensive SSL advanced options handling across all endpoints

Critical fixes for SSL advanced options (alpn, npn, ciphers, ciphersuites, min-ver, max-ver, strict-sni):

1. Frontend GET API (backend/routers/frontend.py)
   - Added all 7 SSL advanced option fields to response
   - Frontend Edit modal will now display these fields
   - Prevents NULL overwrite when user edits frontend

2. Bulk Import Frontend UPDATE (backend/routers/config.py)
   - Added all 7 SSL advanced option fields to UPDATE statement
   - Previously skipped with comment 'MVP: DON'T update SSL settings'
   - Now bulk import re-runs preserve SSL settings

3. Backend Server GET API (backend/routers/backend.py)
   - Added 4 server SSL fields (ssl_sni, ssl_min_ver, ssl_max_ver, ssl_ciphers)
   - SQL SELECT query updated
   - Response object updated
   - Backend Server Edit modal will now display these fields

Root cause: GET APIs were not returning SSL advanced options, causing:
- UI forms to show empty fields
- User edits to overwrite with NULL
- Data loss on subsequent updates

All other endpoints (POST, PUT, INSERT) were already correct.

Impact:
- No more accidental data loss when editing frontends/servers
- Bulk import now preserves SSL advanced options
- UI will correctly display all SSL parameters

Test: Deploy backend, run bulk import, verify ssl_alpn appears in database
This commit is contained in:
Taylan Bakırcıoğlu
2025-11-18 20:36:10 +03:00
committed by taylanbakircioglu
parent 415439cf17
commit 3e2f3e3d9f
3 changed files with 52 additions and 0 deletions
+7
View File
@@ -256,6 +256,7 @@ async def get_backends(cluster_id: Optional[int] = None, include_inactive: bool
servers = await conn.fetch("""
SELECT id, server_name, server_address, server_port, weight, maxconn,
check_enabled, check_port, backup_server, ssl_enabled, ssl_verify, ssl_certificate_id,
ssl_sni, ssl_min_ver, ssl_max_ver, ssl_ciphers,
cookie_value, inter, fall, rise,
is_active, cluster_id,
haproxy_status, haproxy_status_updated_at, backend_name
@@ -266,6 +267,7 @@ async def get_backends(cluster_id: Optional[int] = None, include_inactive: bool
servers = await conn.fetch("""
SELECT id, server_name, server_address, server_port, weight, maxconn,
check_enabled, check_port, backup_server, ssl_enabled, ssl_verify, ssl_certificate_id,
ssl_sni, ssl_min_ver, ssl_max_ver, ssl_ciphers,
cookie_value, inter, fall, rise,
is_active, cluster_id,
haproxy_status, haproxy_status_updated_at, backend_name
@@ -317,6 +319,11 @@ async def get_backends(cluster_id: Optional[int] = None, include_inactive: bool
"ssl_enabled": s.get("ssl_enabled", False),
"ssl_verify": s.get("ssl_verify"),
"ssl_certificate_id": s.get("ssl_certificate_id"),
# CRITICAL FIX: Include SSL advanced options for backend servers
"ssl_sni": s.get("ssl_sni"),
"ssl_min_ver": s.get("ssl_min_ver"),
"ssl_max_ver": s.get("ssl_max_ver"),
"ssl_ciphers": s.get("ssl_ciphers"),
"cookie_value": s.get("cookie_value"),
"inter": s.get("inter"),
"fall": s.get("fall"),
+37
View File
@@ -1777,6 +1777,43 @@ async def bulk_create_entities(
update_values.append(frontend_data["options"])
param_index += 1
# CRITICAL FIX: Update SSL advanced options (alpn, npn, ciphers, etc.)
# These are parsed from bind directive and should be preserved in database
if "ssl_alpn" in frontend_data and frontend_data.get("ssl_alpn") != existing_full.get("ssl_alpn"):
update_fields.append(f"ssl_alpn = ${param_index}")
update_values.append(frontend_data.get("ssl_alpn"))
param_index += 1
if "ssl_npn" in frontend_data and frontend_data.get("ssl_npn") != existing_full.get("ssl_npn"):
update_fields.append(f"ssl_npn = ${param_index}")
update_values.append(frontend_data.get("ssl_npn"))
param_index += 1
if "ssl_ciphers" in frontend_data and frontend_data.get("ssl_ciphers") != existing_full.get("ssl_ciphers"):
update_fields.append(f"ssl_ciphers = ${param_index}")
update_values.append(frontend_data.get("ssl_ciphers"))
param_index += 1
if "ssl_ciphersuites" in frontend_data and frontend_data.get("ssl_ciphersuites") != existing_full.get("ssl_ciphersuites"):
update_fields.append(f"ssl_ciphersuites = ${param_index}")
update_values.append(frontend_data.get("ssl_ciphersuites"))
param_index += 1
if "ssl_min_ver" in frontend_data and frontend_data.get("ssl_min_ver") != existing_full.get("ssl_min_ver"):
update_fields.append(f"ssl_min_ver = ${param_index}")
update_values.append(frontend_data.get("ssl_min_ver"))
param_index += 1
if "ssl_max_ver" in frontend_data and frontend_data.get("ssl_max_ver") != existing_full.get("ssl_max_ver"):
update_fields.append(f"ssl_max_ver = ${param_index}")
update_values.append(frontend_data.get("ssl_max_ver"))
param_index += 1
if "ssl_strict_sni" in frontend_data and frontend_data.get("ssl_strict_sni") != existing_full.get("ssl_strict_sni", False):
update_fields.append(f"ssl_strict_sni = ${param_index}")
update_values.append(frontend_data.get("ssl_strict_sni", False))
param_index += 1
if frontend_data.get("timeout_http_request") and frontend_data["timeout_http_request"] != existing_full["timeout_http_request"]:
update_fields.append(f"timeout_http_request = ${param_index}")
update_values.append(frontend_data["timeout_http_request"])
+8
View File
@@ -337,6 +337,14 @@ async def get_frontends(cluster_id: Optional[int] = None, include_inactive: bool
"ssl_cert_path": f.get("ssl_cert_path"),
"ssl_cert": f.get("ssl_cert"),
"ssl_verify": f.get("ssl_verify", "optional"),
# CRITICAL FIX: Include SSL advanced options (bind SSL parameters)
"ssl_alpn": f.get("ssl_alpn"),
"ssl_npn": f.get("ssl_npn"),
"ssl_ciphers": f.get("ssl_ciphers"),
"ssl_ciphersuites": f.get("ssl_ciphersuites"),
"ssl_min_ver": f.get("ssl_min_ver"),
"ssl_max_ver": f.get("ssl_max_ver"),
"ssl_strict_sni": f.get("ssl_strict_sni", False),
"acl_rules": parse_jsonb_field(f.get("acl_rules"), []),
"redirect_rules": parse_jsonb_field(f.get("redirect_rules"), []),
"use_backend_rules": parse_jsonb_field(f.get("use_backend_rules"), []),