Architektur-Pivot: nginx fällt komplett weg. HAProxy 2.8+ übernimmt
TLS-Termination, L7-Routing per Host-Header und LB. ACME-Webroot
und Management-UI werden von edgeguard-api ausgeliefert (Phase 3
implementiert die zugehörigen Handler); HAProxy proxied
/.well-known/acme-challenge/* und Management-FQDN-Traffic an
127.0.0.1:9443. Eine Distro-Abhängigkeit weniger, ein Renderer
weniger, sauberere Trennung.
Renderer (alle mit Embed-Templates + Tests):
* internal/configgen/ — atomic write + systemctl reload helpers
* internal/haproxy/ — :80 + :443, ACME-ACL, Host-Header-Routing,
Stats-Frontend, api_backend Fallback
* internal/firewall/ — default-deny input, stateful baseline,
SSH-Rate-Limit, :80/:443 accept,
Cluster-Peer-Set für mTLS :8443,
Custom-Rules aus PG
* internal/{squid,wireguard,unbound}/ — Stubs (ErrNotImplemented)
Orchestrator + CLI:
* internal/services/configorch/ — fester Reihenfolge-Run, Stubs
sind soft-skip statt fatal
* cmd/edgeguard-ctl render-config [--no-reload] [--only=svc1,svc2]
Packaging:
* postinst: /etc/edgeguard/nginx raus, /var/lib/edgeguard/acme rein,
self-signed _default.pem via openssl req (damit HAProxy startet
bevor certbot etwas issuet hat)
* control: Depends nginx raus, openssl rein
* edgeguard-ui: dependency auf nginx weg, "Served by edgeguard-api
gin StaticFS"
Live-Smoke: render-config gegen lokale PG schreibt /etc/edgeguard/
haproxy/haproxy.cfg + nftables.d/ruleset.nft korrekt; CRUD-Test aus
Phase 2 läuft weiter unverändert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
79 lines
2.1 KiB
Go
79 lines
2.1 KiB
Go
package haproxy
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/models"
|
|
)
|
|
|
|
func renderView(t *testing.T, v View) string {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
if err := tpl.Execute(&buf, v); err != nil {
|
|
t.Fatalf("template execute: %v", err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func TestRender_BaselineHasFrontendsAndApiBackend(t *testing.T) {
|
|
out := renderView(t, View{})
|
|
for _, w := range []string{
|
|
"frontend public_http",
|
|
"frontend public_https",
|
|
"frontend internal_stats",
|
|
"backend api_backend",
|
|
"server api1 127.0.0.1:9443 check",
|
|
"bind :443 ssl crt /etc/edgeguard/tls/",
|
|
"path_beg /.well-known/acme-challenge/",
|
|
"http-request redirect scheme https",
|
|
} {
|
|
if !strings.Contains(out, w) {
|
|
t.Errorf("missing %q in baseline output:\n%s", w, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRender_DomainRoutesEmitUseBackend(t *testing.T) {
|
|
v := View{
|
|
Backends: []models.Backend{
|
|
{ID: 1, Name: "app", Address: "10.0.0.10", Port: 8080, Active: true},
|
|
{ID: 2, Name: "api", Address: "10.0.0.20", Port: 9000, Active: true},
|
|
},
|
|
Domains: []DomainView{{
|
|
Domain: models.Domain{ID: 1, Name: "example.com", Active: true},
|
|
Routes: []RouteView{
|
|
{PathPrefix: "/", BackendID: 1},
|
|
{PathPrefix: "/api", BackendID: 2},
|
|
},
|
|
}},
|
|
}
|
|
out := renderView(t, v)
|
|
for _, w := range []string{
|
|
"backend eg_backend_1",
|
|
"server app 10.0.0.10:8080",
|
|
"backend eg_backend_2",
|
|
"server api 10.0.0.20:9000",
|
|
"use_backend eg_backend_1 if { hdr(host) -i example.com } { path_beg / }",
|
|
"use_backend eg_backend_2 if { hdr(host) -i example.com } { path_beg /api }",
|
|
} {
|
|
if !strings.Contains(out, w) {
|
|
t.Errorf("missing %q in output:\n%s", w, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRender_HealthCheckPathAddsCheckInter(t *testing.T) {
|
|
hcp := "/health"
|
|
v := View{
|
|
Backends: []models.Backend{
|
|
{ID: 1, Name: "app", Address: "10.0.0.10", Port: 8080, Active: true, HealthCheckPath: &hcp},
|
|
},
|
|
}
|
|
out := renderView(t, v)
|
|
if !strings.Contains(out, "server app 10.0.0.10:8080 check inter 5s") {
|
|
t.Errorf("expected `check inter 5s` for backend with health_check_path:\n%s", out)
|
|
}
|
|
}
|