Closes#13, Closes#14.
This release squashes the v1.4.0 → v1.5.0 development line. v1.4.0
shipped the ACME stability & enterprise audit (Issues #10/#11/#12).
v1.5.0 builds on that foundation with two co-equal headline features
plus a 22-round audit campaign hardening the prior configuration
surface. License remains MIT for v1.5.0 (relicense to AGPL-3.0
lands in v1.5.2).
------------------------------------------------------------------
HEADLINE FEATURE A — ACME Diagnostic Panel (Issue #13)
------------------------------------------------------------------
A live pre-flight + post-failure diagnostic surface for every ACME
order, reachable from the ACME Automation page. The panel exists
to make ACME failures legible to operators who do NOT have shell
access to the API host.
Endpoints (`backend/routers/acme_diagnostics.py`):
POST /api/letsencrypt/orders/{order_id}/diagnostics
Run the full 5-check suite (DNS / port-80 / routing /
account / agents) and humanize the order's `error_detail`
(>=11 RFC-8555 problem types, backwards compatible with
legacy plain-string failures).
POST /api/letsencrypt/orders/{order_id}/diagnostics/
{check_id}/rerun
Re-run a single check in place — used by the "Re-run"
button on every row of the modal's pre-flight table.
GET /api/letsencrypt/orders/{order_id}/events
Merged event timeline combining the typed
`acme_order_events` rows with correlated
`user_activity_logs` entries (resource_type =
'letsencrypt_order' AND resource_id = order_id). The
diagnostic modal auto-tails this timeline every 5 seconds
while open.
Service-level checks (`backend/services/acme_diagnostics.py`):
* DNS resolution via stdlib socket.gethostbyname_ex through
run_in_executor (intentionally avoiding an aiodns runtime
dep for v1.5.0).
* Port-80 HEAD probe, target locked to the order's domains,
success on HTTP 200 OR 404, warns on egress timeout
(corp egress policies routinely blackhole outbound 80 —
fail-hard would be too noisy).
* SSRF guard: probe refuses non-public IPs and surfaces the
skip in the diagnostic result; IPv4-mapped IPv6 normalisation
closes the `::ffff:169.254.169.254` cloud-metadata vector.
* HAProxy routing presence check: matches the order's
cluster_ids to a port-80 HTTP frontend.
* ACME account validity check against `letsencrypt_accounts`.
* Agent presence check (>=1 active agent in target cluster).
* Every sub-check wrapped in a wall-clock timeout to bound
impact on the API event loop.
RBAC: ssl.read for run, ssl.read for events. Per-user 5/min rate
limit on both run and rerun, backed by the (user_id, action,
created_at DESC) composite index.
Frontend (`frontend/src/components/ACMEAutomation.js`):
* "Diagnose" button on every order row + the existing
"stuck order" warning row.
* Modal with two tabs:
- Pre-flight Checks (Antd Table with status pills + Re-run
buttons + humanized error banner)
- Event Log (Antd Timeline with auto-tail polling, scroll-
to-bottom, pause-on-hover)
* Correlation IDs surfaced in error banners and individual
check fail details for backend-log lookup.
------------------------------------------------------------------
HEADLINE FEATURE B — Site Setup Wizard (Issue #14)
------------------------------------------------------------------
A single guided flow that creates a Backend + Servers + HTTP
Frontend (and optional HTTPS Frontend) in one atomic transaction.
Endpoints (`backend/routers/site_wizard.py`):
POST /api/site-wizard/preview — diff-preview the changeset
POST /api/site-wizard/create — atomic execute
POST /api/site-wizard/reject — clean rollback (including
any wizard_staged ACME
orders)
GET /api/site-wizard/drafts — draft persistence
PUT /api/site-wizard/drafts/{id} — save/update
DELETE /api/site-wizard/drafts/{id}
Feature surface:
* One screen captures both backend (mode + servers) AND
frontend (http + optional https + SSL mode) inputs.
* SSL modes: ACME (new order, HTTP-01 only for v1.5.0),
Upload (existing PEM), Existing (link to a stored cert),
or None.
* ACME-staged path: wizard_staged_until watermark on the
`letsencrypt_orders` row defers finalisation until agent
confirmation; per-mode reject cleanly cancels and rolls
back the staged order.
* Live diff preview against the cluster's current generated
config (renderer-evolution noise stripped — track-sc<N>
dedup, per-server cookie strip, defaults-cookie
inheritance, listen-block flattening).
* Draft persistence with PEM stripped at save time (private
keys never round-trip through the drafts table).
* Per-cluster multi-tenancy: drafts and wizard_staged orders
are isolated to the creating user's cluster scope.
Frontend (`frontend/src/components/SiteWizard.js`):
* 4-step Antd Steps flow: Backend → Frontend → SSL → Review.
* Render the live diff preview inline before commit.
* Antd Form-level validation mirrors backend Pydantic
validators (numeric bounds, HAProxy reserved keywords, ALPN
consistency, IPv6 scope-id, domain regex, server name
dedup).
------------------------------------------------------------------
AUDIT CAMPAIGN — Rounds 1 → 22 (Bulgu #1 → #82)
------------------------------------------------------------------
v1.5.0 includes 22 adversarial review passes. Each round produced
its own commit set in the corporate development line; this squash
collapses those into the v1.5.0 release artefact. Highlights:
Round 1-4 Site Wizard core: dry-run parity, single-line
value injection guard, ACL -f pattern-file block,
SSL parity, timeout regex, form-state pin.
Round 5-7 defaults-cookie inheritance, server-named-cookie
guard, fe/be mode mismatch, duplicate server
names, health_check_uri + server_address
validators.
Round 8-10 cookie_name / cookie_options newline-injection
guard, dry-run parity (round 9), TCP-mode HTTP-only
feature blockers.
Round 11 SSL name path traversal + health-check >= 1.
Round 12-13 SSL & ACME deep dive (Bulgu #23-#32).
Round 14 single-line value injection (Bulgu #33).
Round 15-17 ACME multi-tenant UX, numeric bounds, HAProxy
reserved keywords, ALPN/TLS consistency,
all-backup, multi-domain & multi-user enterprise
edges, drain/HSTS/post-completion (Bulgu
#34-#53).
Round 18-21 concurrency, agent state, TCP-mode HTTP-only,
list size caps, IPv6 scope-id, preview account
validation, TCP backend + balance uri reject
(Bulgu #54-#61).
Round 22 FE error visibility + 3x stale-data lockouts,
referential integrity + cascade safety,
authentication & authorization, multi-cluster
isolation, apply_pending_changes concurrency,
script injection + bulk import multi-tenancy,
prefix-stripped signature comparison
(Bulgu #62-#82).
------------------------------------------------------------------
NO CORPORATE-SPECIFIC ARTIFACTS
------------------------------------------------------------------
This squash deliberately sanitises corporate hostnames, container
registry references, and TLS secret names into generic
placeholders (`your-registry.example.com/your-org`,
`haproxy-openmanager*.example.com`, `wildcard-tls`,
`taylanbakircioglu/haproxy-openmanager-*`) so the public artefact
contains no internal infrastructure detail. Pilot / development
history that retained those values stays in the corporate fork
and is NOT part of this commit.
- Fix critical cascading NULL status bug in ACME challenge flow that could cause 404s
- Add defense-in-depth NULL handling across all ACME service methods
- Add new GET /api/letsencrypt/prerequisites endpoint for configuration checks
- Add interactive ACME Setup Guide with step-by-step navigation links
- Add URL-based tab navigation in Settings and SSL Management pages
- Harden retry flow: return clear 409 errors for invalid/cancelled orders
- Allow cancellation of invalid orders (backend + frontend)
- Improve error message extraction with consistent getErrorMsg helper
- Add status filter tabs (Active/Completed/Failed/All) for order list
- Add visual dimming for cancelled/invalid orders
- Add enhanced pagination with size changer and total count
- Add comprehensive diagnostic logging with ACME: prefix
- Update README with ACME architecture docs, quick start guide, and troubleshooting
Resolves#9
Made-with: Cursor
Query agent preserved_listen_blocks dynamically and skip
conflicting entities to prevent HAProxy validation errors.
Falls back to static reserved list if no agent data available.
Remove redundant local 'import json' statements that caused:
"cannot access local variable 'json' where it is not associated with a value"
Root cause: Local import inside try block made 'json' a local variable,
but except clause referenced json.JSONDecodeError before assignment.
Changes:
- Line 197: Remove local import, use global json (line 5)
- Line 514: Remove 'import json as _json', use global json
- Use ValueError instead of json.JSONDecodeError (equivalent, JSONDecodeError
is a subclass of ValueError)
This was causing config generation to fail completely, returning error
message as config content instead of actual HAProxy configuration.
When ssl_enabled is true for a backend server but:
- No ssl_verify option is explicitly set AND
- No CA file (ssl_certificate_id) is specified
HAProxy 2.8+ defaults to 'verify required' which fails without a CA.
Now auto-adding 'verify none' in this case with a warning log.
Users can override this in UI by setting SSL Verification explicitly.
This fix is safe for:
- Bulk Import: Parser already sets ssl_verify='none' when removing ca-file
- Version Diff: Generated config correctly shows 'verify none'
- Restore: DB values unchanged, config regeneration applies same fix
Fixes: 'verify is enabled by default but no CA file specified' error
CRITICAL BUG FIX: Backend server SSL advanced options not in generated HAProxy config
Problem:
- Database has ssl_sni, ssl_min_ver, ssl_max_ver, ssl_ciphers columns ✅
- Config generation code tries to write them to HAProxy config (line 680-687) ✅
- BUT SELECT statement did NOT include these fields ❌
- Result: server.get('ssl_sni') always returned None
Impact:
- User sets 'TLS Min Version: TLSv1.2' for backend server in UI
- Value saved to database correctly
- BUT generated HAProxy config missing 'ssl-min-ver TLSv1.2'
- Agent deploys incomplete config, SSL settings lost!
Solution:
- Added 4 server SSL advanced options to SELECT query (line 616):
- ssl_sni (for SNI hostname)
- ssl_min_ver (minimum TLS version)
- ssl_max_ver (maximum TLS version)
- ssl_ciphers (cipher suite override)
Config Generation Logic:
- Line 680-687: Code already writes these fields to HAProxy config
- Line 616: Now SELECT actually retrieves the values from database
- Example output: 'server backend1 10.0.0.1:443 ssl sni example.com ssl-min-ver TLSv1.2'
Testing:
- After deployment, bulk import a config with backend server SSL options
- Check generated HAProxy config on agent
- Should now see: 'server X ssl ssl-min-ver TLSv1.2 ciphers ...'
Files Changed:
- backend/services/haproxy_config.py: Line 616 SELECT statement
PROBLEM:
- Config generation was producing invalid syntax: use_backend ["..."]
- HAProxy validation failing on agents
- Old code had insufficient type checking for JSONB fields
ROOT CAUSE:
- Missing ELSE branch when use_backend_rules wasn't a list
- No handling for unexpected types (tuple, Record, etc.)
- No debug logging to track type issues
FIX:
- Added comprehensive type checking (str, list, tuple, other)
- Added debug logging to track type and value
- Added fallback parsing for string representation of lists
- Enhanced validation to skip invalid entries
- Prevents generation of invalid HAProxy syntax
IMPACT:
- Fixes validation failure for cluster 7 (demo-cluster)
- Enables proper parsing of JSONB use_backend_rules
- Backward compatible with legacy string format
TESTED:
- Database validation: use_backend_rules is proper JSONB array
- String elements correctly extracted and written
- Invalid types caught and logged
MINIMAL FIX: Enable backend-first workflow (add servers later)
Problem:
1. User creates backend 'deneme-sil' without servers
2. Config generator SKIPs backend (no servers = skip)
3. User adds server 'server1-sil'
4. Config shows: 'server server1-sil 1.1.1.1:1233' WITHOUT backend block
5. HAProxy validation FAILS (server without backend = syntax error)
User Requirement:
- Create backend first (without servers)
- Assign backend to frontend (default_backend)
- Add servers later
- Standard HAProxy workflow
Solution (MINIMAL - 2 small changes):
1. haproxy_config.py (line 453-454):
OLD: Skip backend if no servers (continue)
NEW: Write backend block anyway (remove continue)
Result:
backend deneme-sil
balance roundrobin
mode http
# (no servers yet - backend will show as DOWN)
Valid HAProxy syntax - check
2. cluster.py (line 1600-1607):
OLD: Only mark backends with servers as APPLIED
NEW: Mark ALL backends as APPLIED (servers optional)
Reason: ALL backends are now in config (even without servers)
Why This Is Better Than Previous Approach:
- Only 2 lines changed (vs 50+ lines)
- No complex logic added
- No risk to existing functionality
- HAProxy naturally handles backends without servers (shows as DOWN)
- Aligns with standard HAProxy usage patterns
Test Scenarios:
- Create backend without servers -> Backend block written to config
- Apply -> HAProxy accepts config (backend DOWN)
- Frontend can use backend (use_backend, default_backend)
- Add server later -> Server added to existing backend block
- Apply -> HAProxy accepts, backend goes UP
HAProxy Behavior:
- Backend without servers: DOWN (no available servers)
- Backend with disabled servers: DOWN (all servers disabled)
- Backend with active servers: UP (servers available)
Related: caa21f0 (has_pending_config fix)
Refs: #backend-workflow #server-optional #haproxy-syntax
CRITICAL FIX: Backends without servers were causing HAProxy validation failures
Problem:
- Backend 'silbeni' (ID: 118) oluşturuldu ama server eklenmedi
- Config generator backend'i haproxy.cfg'ye yazdı ama server satırı olmadan
- HAProxy validation FAIL: 'backend has no servers'
- Agent config'i apply etmedi
- Frontend'de backend görünmedi (validation fail nedeniyle)
Root Cause:
- Config generator server olup olmadığını kontrol etmiyordu
- HAProxy en az 1 server gerektirir, yoksa validation fail olur
- Validation fail = agent apply etmez = backend haproxy.cfg'de görünmez
Solution:
- Backend loop başında server pre-check eklendi
- Server yoksa backend config'e yazılmaz ve WARNING log'lanır
- HAProxy validation her zaman başarılı olur (sadece valid backend'ler yazılır)
Impact:
✅ Server olmayan backend'ler artık config'e yazılmayacak
✅ HAProxy validation artık fail olmayacak
✅ Agent successfully apply edecek
✅ Kullanıcı frontend'de backend'i görecek (0/0 servers ⚠️ Empty tag ile)
✅ Kullanıcı server ekledikten sonra Apply yapınca backend haproxy.cfg'ye yazılacak
Testing:
- Server olmayan backend oluştur → Config'e yazılmaz (log: SKIPPING)
- Server ekle → Config'e yazılır
- Apply → Başarılı
Related: a39a5d6 (frontend null/undefined check)
Refs: #backend-validation #haproxy-config-generator
Phase 3 - Single Value Field Validation:
- Frontend.default_backend: Added validation to prevent '[]' causing ALERT
- Frontend.monitor_uri: Added validation for monitor endpoint
- Backend.health_check_uri: Added validation for health check path
- Backend.cookie_name: Added validation for cookie persistence
- Server.server_name: Added validation with fallback to server_id
- Server.server_address: Added validation (critical field, skip if invalid)
All single-value text fields now validate against:
- Empty strings
- '[]', '{}', 'null', 'None' invalid values
- Proper error logging and skipping
Additional improvements:
- Removed all emojis from log messages per user request
- Fixed server_address variable usage consistency
- Added proper error messages for debugging
Comprehensive Backend Audit Results:
- Checked all routers (frontend, backend, waf, ssl, config)
- Checked all models (Pydantic validation)
- Checked all services and utils
- Only one config generation file: haproxy_config.py (FULLY FIXED)
- Template files use static strings (no risk)
- Agent scripts use static templates (no risk)
Total fields validated: 30+ across all entity types
Risk level: ZERO - Complete protection against invalid values
- Skip '[]', '{}', 'null', 'None' strings in redirect_rules, acl_rules, use_backend_rules
- Add validation for request_headers, response_headers, tcp_request_rules
- Prevents 'redirect []' syntax error that causes HAProxy validation failure
- Fixes: parsing [config:70] : error detected in frontend while parsing redirect rule (was '[]')
- All rules now properly filtered before being written to config
Fixed three critical issues with options field implementation:
1. Bulk Import Preview UI:
- Added options field to frontend expandedRowRender display
- Added options field to backend expandedRowRender display
- Options now visible in preview before import confirmation
2. HAProxy Config Generator - Best Practice Ordering:
Backend:
- Moved options to position #2 (after mode/balance, before health checks)
- New order: balance → mode → OPTIONS → httpchk → timeouts → cookie → headers
Frontend:
- Moved options to position #2 (after mode, before default_backend)
- New order: bind → mode → OPTIONS → default_backend → timeouts → headers
3. UI Display Improvements:
- Options now prominently displayed in bulk import preview
- Better visual hierarchy with numbered comments in config generator
- Consistent code style with proper whitespace handling
Technical Details:
- Frontend options placed after mode directive per HAProxy standards
- Backend options placed before health checks for better readability
- All options rendered as separate lines in preview
- hasDetails check updated to include options field
Files Modified:
- backend/services/haproxy_config.py: Config generation order optimized
- frontend/src/components/BulkConfigImport.js: Preview display enhanced
Implemented comprehensive HAProxy options field support for both backend and frontend entities to enable standard HAProxy directives like 'option http-keep-alive', 'option httplog', 'option forwardfor', etc.
Changes:
- Database: Added 'options' TEXT column to backends and frontends tables
- Models: Added options field to BackendConfig, BackendConfigUpdate, and FrontendConfig
- API Endpoints: Updated CREATE, UPDATE, and GET endpoints to handle options field
* Backend: CREATE/UPDATE/GET with options support
* Frontend: CREATE/UPDATE/GET with options support (fixed 5 SELECT queries)
- Config Generator: Added options block generation for both backends and frontends
- Bulk Import Parser:
* Added options field to ParsedBackend and ParsedFrontend dataclasses
* Implemented option directive parsing with validation
* Added unknown option warnings
* Fixed bulk parse response to include options field
- Bulk Import Merge: Added options field comparison in UPDATE logic
- UI Components:
* BackendServers.js: Added options TextArea form field
* FrontendManagement.js: Added options TextArea form field
Features:
- Multi-line options support (newline-separated format)
- Option validation with known HAProxy options list
- Backward compatible (NULL options for existing entities)
- Bulk import support with merge strategy
- Full CRUD support for both manual and bulk operations
Technical Details:
- Format: Newline-separated TEXT field for multiple options
- Validation: Warns about unknown options but allows them
- Config Generation: Each option written as separate directive
- Agent: Standard HAProxy config validation applies
Total: 10 files modified, ~195 lines added, 26 integration points verified
🐛 Critical Bug Fixes:
- Fixed duplicate 'acl' prefix in generated config (was: 'acl acl Name ...')
- Fixed duplicate 'use_backend' prefix in generated config
- Added use_backend directive parsing from bulk import configs
- Fixed redirect_rules list handling (was causing .strip() error)
🔧 Parser Improvements:
- Added use_backend rules parsing (stored as list like ACL rules)
- Changed use_backend_rules field from str to list for consistency
- Parser now captures all use_backend directives with conditions
🎯 Config Generation Improvements:
- Smart prefix detection: only add 'acl' if not already present
- Smart prefix detection: only add 'use_backend' if not already present
- Support both legacy (string) and new (list) format for rules
- Proper JSON parsing with fallback to newline-separated format
✅ HAProxy Validation:
- Generated config now passes HAProxy validation (haproxy -c -f)
- ACL and use_backend directives in correct HAProxy format
- Routing rules properly linked with ACL conditions
Example parsed config:
acl Elasticsearch hdr(host) -i baremetal-elastic.burgan.com.tr
use_backend Elasticsearch if Elasticsearch
Tested with full config including multiple ACLs and routing rules.