feat: Fix HAProxy ACL ordering + Subdomains field

Rewrite HAProxy config generation with correct ACL ordering:
1. L7 HTTP exact matches first (central, wild-cloud app)
2. L4 exact matches (payne.io, civilsociety.dev)
3. L4 wildcard matches (*.cloud.payne.io) — only when subdomains:true
4. Default → L7 termination

Key changes:
- InstanceRoute: removed ExtraDomains, added Subdomains bool
- GenerateOpts: removed CentralDomain (Central is now just another
  HTTPRoute injected by reconciliation), renamed WildcardCert→CertsDir
- Central's domain comes from config, injected as HTTPRoute with
  the actual running port (no more hardcoded 5055)
- Added API.SetPort() so reconciliation knows the running port
- subdomains:false → exact match only (no *.domain shadowing)

New tests: TestGenerate_WithSubdomains, TestGenerate_ACLOrdering
(verifies L7 exact matches come before L4 wildcards).

Fixes: BUG 1 (central routing to wrong port), BUG 2 (wild-cloud
getting traefik cert), BUG 3 (*.payne.io too broad).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-09 20:29:30 +00:00
parent 8874bc6281
commit 548be903e7
5 changed files with 123 additions and 100 deletions

View File

@@ -47,6 +47,7 @@ type API struct {
certbot *certbot.Manager
services *services.Manager // Service registration manager
sseManager *sse.Manager // SSE manager for real-time events
port int // Running API port (for config-driven routes)
}
// NewAPI creates a new Central API handler with all dependencies
@@ -89,6 +90,18 @@ func NewAPI(dataDir, version string, allowedOrigins []string) (*API, error) {
return api, nil
}
// SetPort records the running API port for config-driven HAProxy routes.
func (api *API) SetPort(port int) {
api.port = port
}
func (api *API) getRunningPort() int {
if api.port > 0 {
return api.port
}
return 5055
}
func envOrDefault(key, defaultVal string) string {
if v := os.Getenv(key); v != "" {
slog.Info("using custom config path", "key", key, "path", v)