mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-21 01:53:24 +00:00
fix(ssl): add change detection and validators for SSL advanced options
Critical additions: 1. Change Detection (backend/routers/config.py) - Added SSL advanced options to bulk import change detection logic - Detects changes in ssl_alpn, ssl_npn, ssl_ciphers, ssl_ciphersuites - Detects changes in ssl_min_ver, ssl_max_ver, ssl_strict_sni - Changes will now appear in version diff 2. Pydantic Validators (backend/models/frontend.py, backend/models/backend.py) - TLS version validator: Only allows valid versions (SSLv3, TLSv1.0-1.3) - ALPN protocol validator: Only allows h2, http/1.1, http/1.0, h2c, spdy/* - NPN protocol validator: Only allows http/1.1, http/1.0, spdy/* - Prevents invalid values from being saved to database - HAProxy validation will not fail due to invalid SSL options Impact: - Bulk import will correctly detect SSL option changes - Apply Management diff will show SSL changes - User cannot enter invalid TLS versions or protocols - Improved UX with early validation errors Previous fixes in this series: - Frontend GET API: Added SSL fields to response - Bulk Import UPDATE: Added SSL fields to UPDATE statement - Backend Server GET API: Added SSL fields to response Test: Bulk import with ALPN change → Should see change in version diff
This commit is contained in:
committed by
taylanbakircioglu
parent
3e2f3e3d9f
commit
e8690245af
@@ -25,6 +25,14 @@ class ServerConfig(BaseModel):
|
||||
fall: Optional[int] = None
|
||||
rise: Optional[int] = None
|
||||
is_active: bool = True
|
||||
|
||||
@validator('ssl_min_ver', 'ssl_max_ver')
|
||||
def validate_tls_version(cls, v):
|
||||
if v is not None:
|
||||
valid_versions = ['SSLv3', 'TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
|
||||
if v not in valid_versions:
|
||||
raise ValueError(f'Invalid TLS version: {v}. Must be one of: {", ".join(valid_versions)}')
|
||||
return v
|
||||
|
||||
class BackendConfig(BaseModel):
|
||||
name: str
|
||||
|
||||
@@ -233,6 +233,36 @@ class FrontendConfig(BaseModel):
|
||||
if v is not None and (v < 1 or v > 100000):
|
||||
raise ValueError('Max connections must be between 1 and 100000')
|
||||
return v
|
||||
|
||||
@validator('ssl_min_ver', 'ssl_max_ver')
|
||||
def validate_tls_version(cls, v):
|
||||
if v is not None:
|
||||
valid_versions = ['SSLv3', 'TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']
|
||||
if v not in valid_versions:
|
||||
raise ValueError(f'Invalid TLS version: {v}. Must be one of: {", ".join(valid_versions)}')
|
||||
return v
|
||||
|
||||
@validator('ssl_alpn')
|
||||
def validate_alpn(cls, v):
|
||||
if v is not None and v.strip():
|
||||
# ALPN protocols are comma-separated
|
||||
protocols = [p.strip() for p in v.split(',')]
|
||||
valid_protocols = ['h2', 'http/1.1', 'http/1.0', 'h2c', 'spdy/3', 'spdy/2', 'spdy/1']
|
||||
for proto in protocols:
|
||||
if proto and proto not in valid_protocols:
|
||||
raise ValueError(f'Invalid ALPN protocol: {proto}. Valid protocols: {", ".join(valid_protocols)}')
|
||||
return v
|
||||
|
||||
@validator('ssl_npn')
|
||||
def validate_npn(cls, v):
|
||||
if v is not None and v.strip():
|
||||
# NPN protocols are comma-separated (legacy)
|
||||
protocols = [p.strip() for p in v.split(',')]
|
||||
valid_protocols = ['http/1.1', 'http/1.0', 'spdy/3', 'spdy/2', 'spdy/1']
|
||||
for proto in protocols:
|
||||
if proto and proto not in valid_protocols:
|
||||
raise ValueError(f'Invalid NPN protocol: {proto}. Valid protocols: {", ".join(valid_protocols)}')
|
||||
return v
|
||||
|
||||
@validator('acl_rules')
|
||||
def validate_acl_rules(cls, v):
|
||||
|
||||
@@ -1060,6 +1060,29 @@ async def parse_bulk_config(
|
||||
has_changes = True
|
||||
changes["tcp_request_rules"] = {"old": existing["tcp_request_rules"], "new": frontend["tcp_request_rules"]}
|
||||
|
||||
# CRITICAL: SSL Advanced Options change detection
|
||||
if frontend.get("ssl_alpn") is not None and frontend.get("ssl_alpn") != existing.get("ssl_alpn"):
|
||||
has_changes = True
|
||||
changes["ssl_alpn"] = {"old": existing.get("ssl_alpn"), "new": frontend.get("ssl_alpn")}
|
||||
if frontend.get("ssl_npn") is not None and frontend.get("ssl_npn") != existing.get("ssl_npn"):
|
||||
has_changes = True
|
||||
changes["ssl_npn"] = {"old": existing.get("ssl_npn"), "new": frontend.get("ssl_npn")}
|
||||
if frontend.get("ssl_ciphers") is not None and frontend.get("ssl_ciphers") != existing.get("ssl_ciphers"):
|
||||
has_changes = True
|
||||
changes["ssl_ciphers"] = {"old": existing.get("ssl_ciphers"), "new": frontend.get("ssl_ciphers")}
|
||||
if frontend.get("ssl_ciphersuites") is not None and frontend.get("ssl_ciphersuites") != existing.get("ssl_ciphersuites"):
|
||||
has_changes = True
|
||||
changes["ssl_ciphersuites"] = {"old": existing.get("ssl_ciphersuites"), "new": frontend.get("ssl_ciphersuites")}
|
||||
if frontend.get("ssl_min_ver") is not None and frontend.get("ssl_min_ver") != existing.get("ssl_min_ver"):
|
||||
has_changes = True
|
||||
changes["ssl_min_ver"] = {"old": existing.get("ssl_min_ver"), "new": frontend.get("ssl_min_ver")}
|
||||
if frontend.get("ssl_max_ver") is not None and frontend.get("ssl_max_ver") != existing.get("ssl_max_ver"):
|
||||
has_changes = True
|
||||
changes["ssl_max_ver"] = {"old": existing.get("ssl_max_ver"), "new": frontend.get("ssl_max_ver")}
|
||||
if "ssl_strict_sni" in frontend and frontend.get("ssl_strict_sni") != existing.get("ssl_strict_sni", False):
|
||||
has_changes = True
|
||||
changes["ssl_strict_sni"] = {"old": existing.get("ssl_strict_sni", False), "new": frontend.get("ssl_strict_sni")}
|
||||
|
||||
# Additional frontend fields (timeout_http_request, rate_limit, compression, log_separate, monitor_uri)
|
||||
if frontend.get("timeout_http_request") and frontend["timeout_http_request"] != existing.get("timeout_http_request"):
|
||||
has_changes = True
|
||||
|
||||
Reference in New Issue
Block a user