chore(lint): golangci-lint --fix — misspell + staticcheck-Autofixes (Backlog 142→~98)
Erster Schritt des golangci-lint-Rollouts (non-blocking): 44 misspell + 4 staticcheck automatisch behoben (32 Dateien, nur Tippfehler/mechanisch). build+test grün. Kein Runtime-Change → kein Deploy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,54 +17,54 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/aggregator"
|
||||||
|
chronyrender "git.netcell-it.de/projekte/edgeguard-native/internal/chrony"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster/clustertls"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster/jointoken"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
||||||
firewallrender "git.netcell-it.de/projekte/edgeguard-native/internal/firewall"
|
firewallrender "git.netcell-it.de/projekte/edgeguard-native/internal/firewall"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/haproxy"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/license"
|
|
||||||
licsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/license"
|
|
||||||
chronyrender "git.netcell-it.de/projekte/edgeguard-native/internal/chrony"
|
|
||||||
squidrender "git.netcell-it.de/projekte/edgeguard-native/internal/squid"
|
|
||||||
unboundrender "git.netcell-it.de/projekte/edgeguard-native/internal/unbound"
|
|
||||||
wgrender "git.netcell-it.de/projekte/edgeguard-native/internal/wireguard"
|
|
||||||
kearender "git.netcell-it.de/projekte/edgeguard-native/internal/kea"
|
|
||||||
radiusrender "git.netcell-it.de/projekte/edgeguard-native/internal/freeradius"
|
radiusrender "git.netcell-it.de/projekte/edgeguard-native/internal/freeradius"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/haproxy"
|
||||||
|
kearender "git.netcell-it.de/projekte/edgeguard-native/internal/kea"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/license"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/acme"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/acme"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/alerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/alerts"
|
||||||
|
aptsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/apt"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/audit"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/audit"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backends"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backends"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backendservers"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backendservers"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backup"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backup"
|
||||||
backupremote "git.netcell-it.de/projekte/edgeguard-native/internal/services/backup/remote"
|
backupremote "git.netcell-it.de/projekte/edgeguard-native/internal/services/backup/remote"
|
||||||
dnssvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/dns"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/aggregator"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster/clustertls"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster/jointoken"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/clusterjoin"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/clusterjoin"
|
||||||
aptsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/apt"
|
dhcpsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/dhcp"
|
||||||
|
dnssvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/dns"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/domainheaders"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/domainheaders"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/domains"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/domains"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/firewall"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/firewall"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/firewalllog"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/firewalllog"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/syslogs"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/forwardproxy"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/forwardproxy"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/ipaddresses"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/ipaddresses"
|
||||||
|
licsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/license"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/networkifs"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/networkifs"
|
||||||
ntpsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/ntp"
|
ntpsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/ntp"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/routingrules"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/secrets"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/staticroutes"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/session"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
|
||||||
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
|
|
||||||
dhcpsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/dhcp"
|
|
||||||
oidcsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/oidc"
|
oidcsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/oidc"
|
||||||
radiussvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/radius"
|
radiussvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/radius"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/routingrules"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/secrets"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/session"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/staticroutes"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/syslogs"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
wafsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/waf"
|
wafsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/waf"
|
||||||
|
wgsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/wireguard"
|
||||||
|
squidrender "git.netcell-it.de/projekte/edgeguard-native/internal/squid"
|
||||||
|
unboundrender "git.netcell-it.de/projekte/edgeguard-native/internal/unbound"
|
||||||
|
wgrender "git.netcell-it.de/projekte/edgeguard-native/internal/wireguard"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.2.35"
|
var version = "1.2.35"
|
||||||
@@ -287,7 +287,7 @@ func main() {
|
|||||||
// reload haproxy. Wird in Domains/Backends/RoutingRules-Handler
|
// reload haproxy. Wird in Domains/Backends/RoutingRules-Handler
|
||||||
// injiziert, damit jede Änderung ohne expliziten render-config-
|
// injiziert, damit jede Änderung ohne expliziten render-config-
|
||||||
// Aufruf live geht. Errors werden geloggt, nicht failed
|
// Aufruf live geht. Errors werden geloggt, nicht failed
|
||||||
// (Row schon committed, Operator kann manuell re-triggern).
|
// (Row schon committed, Operator kann manuell re-triggering).
|
||||||
// Maintenance-Endpoints brauchen den Reloader — späte Wiring
|
// Maintenance-Endpoints brauchen den Reloader — späte Wiring
|
||||||
// nachdem haproxyReloader-closure existiert.
|
// nachdem haproxyReloader-closure existiert.
|
||||||
haproxyReloaderForLater := func(ctx context.Context) error {
|
haproxyReloaderForLater := func(ctx context.Context) error {
|
||||||
@@ -296,18 +296,18 @@ func main() {
|
|||||||
systemHdl.WithMaintenance(setupStore, haproxyReloaderForLater)
|
systemHdl.WithMaintenance(setupStore, haproxyReloaderForLater)
|
||||||
|
|
||||||
// Audit-Wiring (Phase Polish): Settings + Auth-Mutationen
|
// Audit-Wiring (Phase Polish): Settings + Auth-Mutationen
|
||||||
// landen jetzt im audit_log. Nodes-id ist die persistente
|
// landen jetzt im audit_log. Nodes-id ist die persistence
|
||||||
// /var/lib/edgeguard/node-id.
|
// /var/lib/edgeguard/node-id.
|
||||||
systemHdl.WithAudit(auditRepo, nodeID)
|
systemHdl.WithAudit(auditRepo, nodeID)
|
||||||
systemHdl.WithDB(pool)
|
systemHdl.WithDB(pool)
|
||||||
systemHdl.WithConfigPreviewers(map[string]func(context.Context) (string, error){
|
systemHdl.WithConfigPreviewers(map[string]func(context.Context) (string, error){
|
||||||
"haproxy": haproxy.New(pool).RenderToString,
|
"haproxy": haproxy.New(pool).RenderToString,
|
||||||
"nftables": firewallrender.New(pool).RenderToString,
|
"nftables": firewallrender.New(pool).RenderToString,
|
||||||
"squid": squidrender.New(pool).RenderToString,
|
"squid": squidrender.New(pool).RenderToString,
|
||||||
"unbound": unboundrender.New(pool).RenderToString,
|
"unbound": unboundrender.New(pool).RenderToString,
|
||||||
"chrony": chronyrender.New(pool).RenderToString,
|
"chrony": chronyrender.New(pool).RenderToString,
|
||||||
"wireguard": wgrender.New(pool, secretsBox).RenderToString,
|
"wireguard": wgrender.New(pool, secretsBox).RenderToString,
|
||||||
})
|
})
|
||||||
setupHdl.WithAudit(auditRepo, nodeID)
|
setupHdl.WithAudit(auditRepo, nodeID)
|
||||||
setupHdl.WithClusterSupport(clusterStore, func(ctx context.Context) error {
|
setupHdl.WithClusterSupport(clusterStore, func(ctx context.Context) error {
|
||||||
return firewallrender.New(pool).Render(ctx)
|
return firewallrender.New(pool).Render(ctx)
|
||||||
@@ -411,7 +411,7 @@ func main() {
|
|||||||
// services whose state feeds the auto-FW-rule generator (DNS
|
// services whose state feeds the auto-FW-rule generator (DNS
|
||||||
// listen-IPs, Squid ACL count, WG listen-port, NTP serve-clients).
|
// listen-IPs, Squid ACL count, WG listen-port, NTP serve-clients).
|
||||||
// Service-Reload-Errors propagieren; FW-Errors werden nur
|
// Service-Reload-Errors propagieren; FW-Errors werden nur
|
||||||
// geloggt (DB-Row ist commited, FW kann nachgezogen werden).
|
// geloggt (DB-Row ist committed, FW kann nachgezogen werden).
|
||||||
withFW := func(svc func(context.Context) error) func(context.Context) error {
|
withFW := func(svc func(context.Context) error) func(context.Context) error {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if err := svc(ctx); err != nil {
|
if err := svc(ctx); err != nil {
|
||||||
@@ -449,14 +449,14 @@ func main() {
|
|||||||
handlers.NewForwardProxyHandler(fwdProxyRepo, auditRepo, nodeID, withFW(squidReloader)).Register(authed)
|
handlers.NewForwardProxyHandler(fwdProxyRepo, auditRepo, nodeID, withFW(squidReloader)).Register(authed)
|
||||||
|
|
||||||
// Unbound DNS reload — re-render edgeguard.conf + restart
|
// Unbound DNS reload — re-render edgeguard.conf + restart
|
||||||
// unbound. Listen-IPs triggern Auto-FW-Rule für udp/tcp 53.
|
// unbound. Listen-IPs triggering Auto-FW-Rule für udp/tcp 53.
|
||||||
unboundReloader := func(ctx context.Context) error {
|
unboundReloader := func(ctx context.Context) error {
|
||||||
return unboundrender.New(pool).Render(ctx)
|
return unboundrender.New(pool).Render(ctx)
|
||||||
}
|
}
|
||||||
handlers.NewDNSHandler(dnsRepo, auditRepo, nodeID, withFW(unboundReloader)).Register(authed)
|
handlers.NewDNSHandler(dnsRepo, auditRepo, nodeID, withFW(unboundReloader)).Register(authed)
|
||||||
|
|
||||||
// Chrony NTP reload — re-render edgeguard.conf + restart chrony.
|
// Chrony NTP reload — re-render edgeguard.conf + restart chrony.
|
||||||
// Listen-IPs + serve_clients triggern Auto-FW-Rule für udp/123.
|
// Listen-IPs + serve_clients triggering Auto-FW-Rule für udp/123.
|
||||||
chronyReloader := func(ctx context.Context) error {
|
chronyReloader := func(ctx context.Context) error {
|
||||||
return chronyrender.New(pool).Render(ctx)
|
return chronyrender.New(pool).Render(ctx)
|
||||||
}
|
}
|
||||||
@@ -502,7 +502,7 @@ func main() {
|
|||||||
// Startup-Render nftables: stellt sicher dass Template-Änderungen
|
// Startup-Render nftables: stellt sicher dass Template-Änderungen
|
||||||
// aus einem Update (z.B. neue WireGuard forward-Chain-Auto-Regel)
|
// aus einem Update (z.B. neue WireGuard forward-Chain-Auto-Regel)
|
||||||
// sofort nach dem API-Restart aktiv werden — ohne dass der
|
// sofort nach dem API-Restart aktiv werden — ohne dass der
|
||||||
// Operator manuell eine Mutation triggern müsste. nft -f ist
|
// Operator manuell eine Mutation triggering müsste. nft -f ist
|
||||||
// idempotent und atomar; kein Dienst wird neu gestartet.
|
// idempotent und atomar; kein Dienst wird neu gestartet.
|
||||||
go func() {
|
go func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/chrony"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/chrony"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/freeradius"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/kea"
|
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/firewall"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/firewall"
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/freeradius"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/haproxy"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/haproxy"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/kea"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/keepalived"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/keepalived"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/configorch"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/configorch"
|
||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/secrets"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/secrets"
|
||||||
@@ -88,7 +88,7 @@ func cmdRenderConfig(args []string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results, runErr := configorch.Run(ctx, gens, only)
|
results, runErr := configorch.Run(ctx, gens, only)
|
||||||
fmt.Print(configorch.Summarise(results))
|
fmt.Print(configorch.Summarize(results))
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
fmt.Fprintln(os.Stderr, "render-config aborted:", runErr)
|
fmt.Fprintln(os.Stderr, "render-config aborted:", runErr)
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ const (
|
|||||||
// ntpSyncCheckInterval — alle 10 Minuten chronyc tracking aufrufen.
|
// ntpSyncCheckInterval — alle 10 Minuten chronyc tracking aufrufen.
|
||||||
// Keine Sync bedeutet: Uhr driftet → TLS-Cert-Prüfung schlägt fehl
|
// Keine Sync bedeutet: Uhr driftet → TLS-Cert-Prüfung schlägt fehl
|
||||||
// wenn die Abweichung > Toleranz des Gegenstücks (i.d.R. ±1 min),
|
// wenn die Abweichung > Toleranz des Gegenstücks (i.d.R. ±1 min),
|
||||||
// JWT-Ablauf inkonsistent, Cluster-Split-Brain möglich. Dedupe 1h
|
// JWT-Ablauf inconsistent, Cluster-Split-Brain möglich. Dedupe 1h
|
||||||
// damit ein kurzer Upstream-Ausfall (Reboot, DHCP-Pause) keinen
|
// damit ein kurzer Upstream-Ausfall (Reboot, DHCP-Pause) keinen
|
||||||
// Alert-Regen produziert.
|
// Alert-Regen produziert.
|
||||||
ntpSyncCheckInterval = 10 * time.Minute
|
ntpSyncCheckInterval = 10 * time.Minute
|
||||||
@@ -140,7 +140,7 @@ const (
|
|||||||
// ist der Tunnel effektiv tot — Traffic droht lautlos. Dedupe 30min
|
// ist der Tunnel effektiv tot — Traffic droht lautlos. Dedupe 30min
|
||||||
// pro Tunnel damit schnell wiederhergestellte Tunnels nur einmal feuern.
|
// pro Tunnel damit schnell wiederhergestellte Tunnels nur einmal feuern.
|
||||||
wgTunnelCheckInterval = 5 * time.Minute
|
wgTunnelCheckInterval = 5 * time.Minute
|
||||||
wgStaleSec = int64(5 * 60) // 5 Minuten ohne Handshake = tot
|
wgStaleSec = int64(5 * 60) // 5 Minuten ohne Handshake = tot
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -241,7 +241,7 @@ func main() {
|
|||||||
ntpSyncTick := time.NewTicker(ntpSyncCheckInterval)
|
ntpSyncTick := time.NewTicker(ntpSyncCheckInterval)
|
||||||
defer ntpSyncTick.Stop()
|
defer ntpSyncTick.Stop()
|
||||||
// Kein Initial-Check bei Boot: chrony braucht nach dem Start
|
// Kein Initial-Check bei Boot: chrony braucht nach dem Start
|
||||||
// einige Sekunden bis zur ersten Synchronisation — ein
|
// einige Sekunden bis zur ersten Synchronization — ein
|
||||||
// sofortiger Check würde immer feuern.
|
// sofortiger Check würde immer feuern.
|
||||||
|
|
||||||
wgTunnelTick := time.NewTicker(wgTunnelCheckInterval)
|
wgTunnelTick := time.NewTicker(wgTunnelCheckInterval)
|
||||||
@@ -682,7 +682,10 @@ func runBackendDownCheck(ctx context.Context, pool *pgxpool.Pool, a *alerts.Serv
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type srvEntry struct{ status string; hasCheck bool }
|
type srvEntry struct {
|
||||||
|
status string
|
||||||
|
hasCheck bool
|
||||||
|
}
|
||||||
byBackend := map[string][]srvEntry{}
|
byBackend := map[string][]srvEntry{}
|
||||||
colIdx := map[string]int{}
|
colIdx := map[string]int{}
|
||||||
scanner := bufio.NewScanner(conn)
|
scanner := bufio.NewScanner(conn)
|
||||||
@@ -1071,7 +1074,7 @@ func runRenewer(ctx context.Context, r *certrenewer.Service, a *alerts.Service,
|
|||||||
_, _ = a.Fire(ctx, "cert.renew_failed", alerts.SeverityError,
|
_, _ = a.Fire(ctx, "cert.renew_failed", alerts.SeverityError,
|
||||||
"Cert-Renewal fehlgeschlagen: "+domain,
|
"Cert-Renewal fehlgeschlagen: "+domain,
|
||||||
"Let's Encrypt Erneuerung für "+domain+" ist fehlgeschlagen. "+
|
"Let's Encrypt Erneuerung für "+domain+" ist fehlgeschlagen. "+
|
||||||
"Prüfe ACME-Konfiguration und DNS-Erreichbarkeit. "+
|
"Prüfe ACME-Configuration und DNS-Erreichbarkeit. "+
|
||||||
"Nächster Versuch beim nächsten Renewer-Tick (alle 6h).")
|
"Nächster Versuch beim nächsten Renewer-Tick (alle 6h).")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func main() {
|
|||||||
slog.Error("waf: SPOE agent stopped", "error", err)
|
slog.Error("waf: SPOE agent stopped", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
// Graceful shutdown (ctx cancelled): gepufferte Alerts flushen.
|
// Graceful shutdown (ctx canceled): gepufferte Alerts flushen.
|
||||||
alertWriter.Close()
|
alertWriter.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func splitCSV(s string) []string {
|
|||||||
// filterNonLoopback wirft 127.x / ::1 raus — wenn NUR localhost im
|
// filterNonLoopback wirft 127.x / ::1 raus — wenn NUR localhost im
|
||||||
// listen_addresses ist, lassen wir den bindaddress-Block weg und
|
// listen_addresses ist, lassen wir den bindaddress-Block weg und
|
||||||
// chrony bindet auf alle Interfaces (default), was für eine reine
|
// chrony bindet auf alle Interfaces (default), was für eine reine
|
||||||
// Client-Konfiguration nicht stört.
|
// Client-Configuration nicht stört.
|
||||||
func filterNonLoopback(in []string) []string {
|
func filterNonLoopback(in []string) []string {
|
||||||
out := []string{}
|
out := []string{}
|
||||||
for _, ip := range in {
|
for _, ip := range in {
|
||||||
|
|||||||
@@ -2,18 +2,19 @@
|
|||||||
// für Node-to-Node mTLS-Kommunikation.
|
// für Node-to-Node mTLS-Kommunikation.
|
||||||
//
|
//
|
||||||
// Layout on disk:
|
// Layout on disk:
|
||||||
// /var/lib/edgeguard/cluster-tls/ca.crt (0644)
|
//
|
||||||
// /var/lib/edgeguard/cluster-tls/ca.key (0600, edgeguard:edgeguard)
|
// /var/lib/edgeguard/cluster-tls/ca.crt (0644)
|
||||||
// /var/lib/edgeguard/cluster-tls/peer.crt (0644) — diese Node
|
// /var/lib/edgeguard/cluster-tls/ca.key (0600, edgeguard:edgeguard)
|
||||||
// /var/lib/edgeguard/cluster-tls/peer.key (0600, edgeguard:edgeguard)
|
// /var/lib/edgeguard/cluster-tls/peer.crt (0644) — diese Node
|
||||||
|
// /var/lib/edgeguard/cluster-tls/peer.key (0600, edgeguard:edgeguard)
|
||||||
//
|
//
|
||||||
// Workflow:
|
// Workflow:
|
||||||
// * Erste Node (cluster founder): InitCA generiert CA, dann EnsureSelfSigned
|
// - Erste Node (cluster founder): InitCA generiert CA, dann EnsureSelfSigned
|
||||||
// erstellt + signiert ihren eigenen peer.crt mit eigener CA.
|
// erstellt + signiert ihren eigenen peer.crt mit eigener CA.
|
||||||
// * Joining Node: lädt CA-Cert vom Primary, generiert lokal CSR, POSTet
|
// - Joining Node: lädt CA-Cert vom Primary, generiert lokal CSR, POSTet
|
||||||
// ihn mit cluster-join-token zu /api/v1/cluster/issue-cert; Primary
|
// ihn mit cluster-join-token zu /api/v1/cluster/issue-cert; Primary
|
||||||
// signiert via SignCSR und liefert peer.crt zurück. (Phase 3.4.)
|
// signiert via SignCSR und liefert peer.crt zurück. (Phase 3.4.)
|
||||||
// * Single-Node: InitCA + EnsureSelfSigned werden beim API-Boot
|
// - Single-Node: InitCA + EnsureSelfSigned werden beim API-Boot
|
||||||
// idempotent gerufen; Listener auf :8443 kann sofort hochfahren.
|
// idempotent gerufen; Listener auf :8443 kann sofort hochfahren.
|
||||||
//
|
//
|
||||||
// Pattern 1:1 aus mail-gateway/internal/services/clustertls/clustertls.go,
|
// Pattern 1:1 aus mail-gateway/internal/services/clustertls/clustertls.go,
|
||||||
@@ -73,8 +74,8 @@ func (s *Store) HasPeer() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InitCA generiert die Cluster-CA falls noch keine existiert. Idempotent.
|
// InitCA generiert die Cluster-CA falls noch keine existiert. Idempotent.
|
||||||
// organisation landet im Subject — typischerweise die FQDN-Domain.
|
// organization landet im Subject — typischerweise die FQDN-Domain.
|
||||||
func (s *Store) InitCA(organisation string, now func() time.Time) error {
|
func (s *Store) InitCA(organization string, now func() time.Time) error {
|
||||||
if s.HasCA() {
|
if s.HasCA() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -93,7 +94,7 @@ func (s *Store) InitCA(organisation string, now func() time.Time) error {
|
|||||||
SerialNumber: serial,
|
SerialNumber: serial,
|
||||||
Subject: pkix.Name{
|
Subject: pkix.Name{
|
||||||
CommonName: "EdgeGuard Cluster CA",
|
CommonName: "EdgeGuard Cluster CA",
|
||||||
Organization: []string{organisation},
|
Organization: []string{organization},
|
||||||
},
|
},
|
||||||
NotBefore: now().UTC(),
|
NotBefore: now().UTC(),
|
||||||
NotAfter: now().Add(caValidity).UTC(),
|
NotAfter: now().Add(caValidity).UTC(),
|
||||||
@@ -325,12 +326,12 @@ func (s *Store) CACertPEM() (string, error) {
|
|||||||
// CertInfo: zusammengefasste Cert-Metadata für UI-Status. days_remaining
|
// CertInfo: zusammengefasste Cert-Metadata für UI-Status. days_remaining
|
||||||
// kann negativ sein wenn der Cert schon abgelaufen ist.
|
// kann negativ sein wenn der Cert schon abgelaufen ist.
|
||||||
type CertInfo struct {
|
type CertInfo struct {
|
||||||
CommonName string `json:"common_name"`
|
CommonName string `json:"common_name"`
|
||||||
NotBefore time.Time `json:"not_before"`
|
NotBefore time.Time `json:"not_before"`
|
||||||
NotAfter time.Time `json:"not_after"`
|
NotAfter time.Time `json:"not_after"`
|
||||||
DaysRemaining int `json:"days_remaining"`
|
DaysRemaining int `json:"days_remaining"`
|
||||||
IsCA bool `json:"is_ca"`
|
IsCA bool `json:"is_ca"`
|
||||||
SerialHex string `json:"serial_hex"`
|
SerialHex string `json:"serial_hex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeerCertInfo liefert die Metadata des eigenen peer.crt. Wenn keiner
|
// PeerCertInfo liefert die Metadata des eigenen peer.crt. Wenn keiner
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
// /etc/edgeguard/node.conf — node-lokale, NIEMALS zwischen Cluster-
|
// /etc/edgeguard/node.conf — node-lokale, NIEMALS zwischen Cluster-
|
||||||
// Peers replizierte Konfiguration. Hält die Identitäts-Werte die jeden
|
// Peers replizierte Configuration. Hält die Identitäts-Werte die jeden
|
||||||
// Node einzigartig machen:
|
// Node einzigartig machen:
|
||||||
//
|
//
|
||||||
// NODE_ID eindeutige UUID (autogeneriert in EnsureNodeID; hier
|
// NODE_ID eindeutige UUID (autogeneriert in EnsureNodeID; hier
|
||||||
@@ -91,7 +91,7 @@ func LoadLocalConfig(path string) (*LocalConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveLocalConfig schreibt die Datei atomic + 0644 root:root.
|
// SaveLocalConfig schreibt die Datei atomic + 0644 root:root.
|
||||||
// Aufrufer ist normalerweise edgeguard-ctl unter Operator-Privilegien.
|
// Aufrufer ist normalerweise edgeguard-ctl unter Operator-Privilege.
|
||||||
func SaveLocalConfig(path string, c *LocalConfig) error {
|
func SaveLocalConfig(path string, c *LocalConfig) error {
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = DefaultLocalConfigPath
|
path = DefaultLocalConfigPath
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Name returns a stable identifier ("haproxy", "nftables", …)
|
// Name returns a stable identifier ("haproxy", "nftables", …)
|
||||||
// used in CLI output and audit logs. Render does the actual write +
|
// used in CLI output and audit logs. Render does the actual write +
|
||||||
// reload work; ctx may be cancelled (e.g. orchestrator timeout).
|
// reload work; ctx may be canceled (e.g. orchestrator timeout).
|
||||||
type Generator interface {
|
type Generator interface {
|
||||||
Name() string
|
Name() string
|
||||||
Render(ctx context.Context) error
|
Render(ctx context.Context) error
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ type HubItem struct {
|
|||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status summarises the runtime state of the CrowdSec stack on this node.
|
// Status summarizes the runtime state of the CrowdSec stack on this node.
|
||||||
type Status struct {
|
type Status struct {
|
||||||
Installed bool `json:"installed"`
|
Installed bool `json:"installed"`
|
||||||
AgentRunning bool `json:"agent_running"`
|
AgentRunning bool `json:"agent_running"`
|
||||||
@@ -362,15 +362,7 @@ func Machines(ctx context.Context) ([]Machine, error) {
|
|||||||
}
|
}
|
||||||
result := make([]Machine, len(raw))
|
result := make([]Machine, len(raw))
|
||||||
for i, r := range raw {
|
for i, r := range raw {
|
||||||
result[i] = Machine{
|
result[i] = Machine(r)
|
||||||
MachineID: r.MachineID,
|
|
||||||
CreatedAt: r.CreatedAt,
|
|
||||||
UpdatedAt: r.UpdatedAt,
|
|
||||||
LastPush: r.LastPush,
|
|
||||||
IsValidated: r.IsValidated,
|
|
||||||
Version: r.Version,
|
|
||||||
Status: r.Status,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import "testing"
|
|||||||
// startup, the API restart-looped, the cluster rolling-upgrade hung.
|
// startup, the API restart-looped, the cluster rolling-upgrade hung.
|
||||||
//
|
//
|
||||||
// Cheap assertion that runs as part of `go test ./...` — fails the
|
// Cheap assertion that runs as part of `go test ./...` — fails the
|
||||||
// build before `make deb` ever produces an artefact, so the bad
|
// build before `make deb` ever produces an artifact, so the bad
|
||||||
// version never reaches the APT registry. Same logic also runs at
|
// version never reaches the APT registry. Same logic also runs at
|
||||||
// service start via Migrate() and via `edgeguard-ctl migrate check`
|
// service start via Migrate() and via `edgeguard-ctl migrate check`
|
||||||
// in postinst (defense in depth).
|
// in postinst (defense in depth).
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
// + groups, policy rules, nat rules, ha_nodes peer IPs.
|
// + groups, policy rules, nat rules, ha_nodes peer IPs.
|
||||||
// 2. Each rule and nat-rule is "resolved" — group references
|
// 2. Each rule and nat-rule is "resolved" — group references
|
||||||
// replaced with their primitive members, FQDNs left as comments
|
// replaced with their primitive members, FQDNs left as comments
|
||||||
// (Phase-3 DNS-resolution sidecar will materialise them).
|
// (Phase-3 DNS-resolution sidecar will materialize them).
|
||||||
// 3. The template emits one nft file with: zone-iface sets, peer
|
// 3. The template emits one nft file with: zone-iface sets, peer
|
||||||
// sets, default-deny baseline, forward + input chains carrying
|
// sets, default-deny baseline, forward + input chains carrying
|
||||||
// the resolved rules (priority-sorted), nat prerouting +
|
// the resolved rules (priority-sorted), nat prerouting +
|
||||||
@@ -150,12 +150,12 @@ type AutoFWRule struct {
|
|||||||
Proto string
|
Proto string
|
||||||
Port int
|
Port int
|
||||||
DstIP string
|
DstIP string
|
||||||
L3 string // "ip"/"ip6" — gesetzt für DstIP-Rules (Familie); leer = agnostisch
|
L3 string // "ip"/"ip6" — gesetzt für DstIP-Rules (Familie); leer = agnostic
|
||||||
Iface string // optional: scope auf ein iifname (z.B. DHCP udp/67 nur auf LAN)
|
Iface string // optional: scope auf ein iifname (z.B. DHCP udp/67 nur auf LAN)
|
||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuleLeg is one materialised nft policy line.
|
// RuleLeg is one materialized nft policy line.
|
||||||
type RuleLeg struct {
|
type RuleLeg struct {
|
||||||
RuleID int64
|
RuleID int64
|
||||||
Action string
|
Action string
|
||||||
@@ -166,7 +166,7 @@ type RuleLeg struct {
|
|||||||
DstIfaces []string
|
DstIfaces []string
|
||||||
SrcAddrs []string
|
SrcAddrs []string
|
||||||
DstAddrs []string
|
DstAddrs []string
|
||||||
// L3 ist "ip" (IPv4) oder "ip6" (IPv6) für das Adress-Matching —
|
// L3 ist "ip" (IPv4) oder "ip6" (IPv6) für das Address-Matching —
|
||||||
// gesetzt, sobald SrcAddrs/DstAddrs nicht leer sind. Bei adresslosen
|
// gesetzt, sobald SrcAddrs/DstAddrs nicht leer sind. Bei adresslosen
|
||||||
// Regeln bleibt es "" (familienagnostisch, kein ip/ip6-Match).
|
// Regeln bleibt es "" (familienagnostisch, kein ip/ip6-Match).
|
||||||
L3 string
|
L3 string
|
||||||
@@ -560,7 +560,7 @@ func splitByFamily(exprs []string) (v4, v6 []string) {
|
|||||||
return v4, v6
|
return v4, v6
|
||||||
}
|
}
|
||||||
|
|
||||||
// serviceL3: icmp ist v4-only, icmpv6 v6-only, tcp/udp/leer agnostisch.
|
// serviceL3: icmp ist v4-only, icmpv6 v6-only, tcp/udp/leer agnostic.
|
||||||
func serviceL3(svc ResolvedService) string {
|
func serviceL3(svc ResolvedService) string {
|
||||||
switch svc.Proto {
|
switch svc.Proto {
|
||||||
case "icmp":
|
case "icmp":
|
||||||
@@ -608,7 +608,7 @@ func expandFamilyLegs(r ResolvedRule, svc ResolvedService, hasSvc bool) []RuleLe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(r.SrcAddrs) == 0 && len(r.DstAddrs) == 0 {
|
if len(r.SrcAddrs) == 0 && len(r.DstAddrs) == 0 {
|
||||||
// Kein Adress-Match → eine Zeile, L3 leer. Die Proto-Render-Logik
|
// Kein Address-Match → eine Zeile, L3 leer. Die Proto-Render-Logik
|
||||||
// im Template setzt icmp/icmpv6 selbst familienkorrekt.
|
// im Template setzt icmp/icmpv6 selbst familienkorrekt.
|
||||||
return []RuleLeg{base}
|
return []RuleLeg{base}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ func renderView(t *testing.T) string {
|
|||||||
Legs: []RuleLeg{
|
Legs: []RuleLeg{
|
||||||
{RuleID: 1, Action: "accept", L3: "ip", SrcAddrs: []string{"10.0.0.0/24"}, Service: ResolvedService{Proto: "tcp", PortStart: 443}},
|
{RuleID: 1, Action: "accept", L3: "ip", SrcAddrs: []string{"10.0.0.0/24"}, Service: ResolvedService{Proto: "tcp", PortStart: 443}},
|
||||||
{RuleID: 1, Action: "accept", L3: "ip6", SrcAddrs: []string{"fd00::/64"}, Service: ResolvedService{Proto: "tcp", PortStart: 443}},
|
{RuleID: 1, Action: "accept", L3: "ip6", SrcAddrs: []string{"fd00::/64"}, Service: ResolvedService{Proto: "tcp", PortStart: 443}},
|
||||||
{RuleID: 2, Action: "accept", Service: ResolvedService{Proto: "icmpv6"}}, // adresslos, agnostisch
|
{RuleID: 2, Action: "accept", Service: ResolvedService{Proto: "icmpv6"}}, // adresslos, agnostic
|
||||||
},
|
},
|
||||||
NATRules: []ResolvedNATRule{
|
NATRules: []ResolvedNATRule{
|
||||||
{ID: 5, Kind: "dnat", L3: "ip6", DstCIDR: "2001:db8::/64", Proto: "tcp", DPortStart: 80, TargetAddr: "fd00::2", TargetHost: "[fd00::2]", TargetPortStart: 8080},
|
{ID: 5, Kind: "dnat", L3: "ip6", DstCIDR: "2001:db8::/64", Proto: "tcp", DPortStart: 80, TargetAddr: "fd00::2", TargetHost: "[fd00::2]", TargetPortStart: 8080},
|
||||||
@@ -126,11 +126,11 @@ func renderView(t *testing.T) string {
|
|||||||
func TestTemplate_v6AndV4Render(t *testing.T) {
|
func TestTemplate_v6AndV4Render(t *testing.T) {
|
||||||
out := renderView(t)
|
out := renderView(t)
|
||||||
mustContain := []string{
|
mustContain := []string{
|
||||||
"ip saddr { 10.0.0.0/24 }", // v4-Regel unverändert
|
"ip saddr { 10.0.0.0/24 }", // v4-Regel unverändert
|
||||||
"ip6 saddr { fd00::/64 }", // v6-Regel
|
"ip6 saddr { fd00::/64 }", // v6-Regel
|
||||||
"ip6 daddr 2001:db8::/64", // v6-DNAT-Match
|
"ip6 daddr 2001:db8::/64", // v6-DNAT-Match
|
||||||
"dnat to [fd00::2]:8080", // v6-DNAT-Target geklammert
|
"dnat to [fd00::2]:8080", // v6-DNAT-Target geklammert
|
||||||
"dnat to 10.0.0.5:80", // v4-DNAT-Target unverändert
|
"dnat to 10.0.0.5:80", // v4-DNAT-Target unverändert
|
||||||
"ip6 saddr fd00::/64 snat to 2001:db8::99",
|
"ip6 saddr fd00::/64 snat to 2001:db8::99",
|
||||||
`oifname "wg7" ip6 saddr fd00:99::/64 masquerade`,
|
`oifname "wg7" ip6 saddr fd00:99::/64 masquerade`,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ type ClusterHandler struct {
|
|||||||
Store *cluster.Store
|
Store *cluster.Store
|
||||||
LocalID string
|
LocalID string
|
||||||
Aggregator *aggregator.Aggregator
|
Aggregator *aggregator.Aggregator
|
||||||
Version string // laufende Binary-Version, für Rolling-Update-Koordination
|
Version string // laufende Binary-Version, für Rolling-Update-Coordination
|
||||||
|
|
||||||
// TLSStore + Tokens: optional, gesetzt bei Phase 3.4. Erlauben das
|
// TLSStore + Tokens: optional, gesetzt bei Phase 3.4. Erlauben das
|
||||||
// Generieren von Join-Tokens und das Issue-Cert für joining Peers.
|
// Generieren von Join-Tokens und das Issue-Cert für joining Peers.
|
||||||
@@ -56,7 +56,7 @@ func NewClusterHandler(store *cluster.Store, localID string) *ClusterHandler {
|
|||||||
return &ClusterHandler{Store: store, LocalID: localID}
|
return &ClusterHandler{Store: store, LocalID: localID}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAggregator: optionale Aggregator-Konfiguration. Nur wenn vorhanden
|
// WithAggregator: optionale Aggregator-Configuration. Nur wenn vorhanden
|
||||||
// wird /cluster/system/load die Peers via mTLS abklappern.
|
// wird /cluster/system/load die Peers via mTLS abklappern.
|
||||||
func (h *ClusterHandler) WithAggregator(a *aggregator.Aggregator) *ClusterHandler {
|
func (h *ClusterHandler) WithAggregator(a *aggregator.Aggregator) *ClusterHandler {
|
||||||
h.Aggregator = a
|
h.Aggregator = a
|
||||||
@@ -104,7 +104,7 @@ func (h *ClusterHandler) Register(rg *gin.RouterGroup) {
|
|||||||
// DeleteNode entfernt einen Peer aus ha_nodes. Verweigert für die
|
// DeleteNode entfernt einen Peer aus ha_nodes. Verweigert für die
|
||||||
// lokale Node (LocalID) — die kannst du nicht via UI löschen, sonst
|
// lokale Node (LocalID) — die kannst du nicht via UI löschen, sonst
|
||||||
// kommt der nächste Heartbeat-Tick die Row wieder anlegen oder
|
// kommt der nächste Heartbeat-Tick die Row wieder anlegen oder
|
||||||
// die Cluster-Page wird inkonsistent.
|
// die Cluster-Page wird inconsistent.
|
||||||
//
|
//
|
||||||
// Nach erfolgreichem Delete triggert der PeerReloader (falls gesetzt)
|
// Nach erfolgreichem Delete triggert der PeerReloader (falls gesetzt)
|
||||||
// einen Firewall-Render — peer_ipv4-Set verliert die IP, der entfernte
|
// einen Firewall-Render — peer_ipv4-Set verliert die IP, der entfernte
|
||||||
@@ -159,7 +159,7 @@ func (h *ClusterHandler) GetVIPSettings(c *gin.Context) {
|
|||||||
response.OK(c, cs)
|
response.OK(c, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateVIPSettings speichert die VIP/VRRP-Konfiguration und triggert
|
// UpdateVIPSettings speichert die VIP/VRRP-Configuration und triggert
|
||||||
// einen Keepalived-Config-Render. Viewer-Schutz via RequireAdminForMutations-
|
// einen Keepalived-Config-Render. Viewer-Schutz via RequireAdminForMutations-
|
||||||
// Middleware auf der authed-Group — kein Extra-Check nötig.
|
// Middleware auf der authed-Group — kein Extra-Check nötig.
|
||||||
func (h *ClusterHandler) UpdateVIPSettings(c *gin.Context) {
|
func (h *ClusterHandler) UpdateVIPSettings(c *gin.Context) {
|
||||||
@@ -338,7 +338,7 @@ func (h *ClusterHandler) WithPeerReloader(r PeerReloader) *ClusterHandler {
|
|||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithVersion: setzt die laufende Binary-Version für Rolling-Update-Koordination.
|
// WithVersion: setzt die laufende Binary-Version für Rolling-Update-Coordination.
|
||||||
func (h *ClusterHandler) WithVersion(v string) *ClusterHandler {
|
func (h *ClusterHandler) WithVersion(v string) *ClusterHandler {
|
||||||
h.Version = v
|
h.Version = v
|
||||||
return h
|
return h
|
||||||
@@ -359,7 +359,7 @@ type ClusterStatus struct {
|
|||||||
LocalID string `json:"local_id"`
|
LocalID string `json:"local_id"`
|
||||||
LocalNode *models.HANode `json:"local_node,omitempty"`
|
LocalNode *models.HANode `json:"local_node,omitempty"`
|
||||||
Peers []models.HANode `json:"peers"`
|
Peers []models.HANode `json:"peers"`
|
||||||
Mode string `json:"mode"` // "single-node" | "cluster"
|
Mode string `json:"mode"` // "single-node" | "cluster"
|
||||||
Health string `json:"health"` // "ok" | "degraded" | "split-brain"
|
Health string `json:"health"` // "ok" | "degraded" | "split-brain"
|
||||||
DriftFound bool `json:"drift_found"`
|
DriftFound bool `json:"drift_found"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
@@ -822,7 +822,7 @@ func (h *ClusterHandler) CertStatus(c *gin.Context) {
|
|||||||
//
|
//
|
||||||
// Nach Renew muss edgeguard-api restartet werden damit der Agent-
|
// Nach Renew muss edgeguard-api restartet werden damit der Agent-
|
||||||
// Listener das neue Cert in seinen TLS-Config-Snapshot lädt — wir
|
// Listener das neue Cert in seinen TLS-Config-Snapshot lädt — wir
|
||||||
// triggern das NICHT automatisch (würde die HTTP-Response abreißen);
|
// triggering das NICHT automatisch (würde die HTTP-Response abreißen);
|
||||||
// stattdessen liefern wir einen Hinweis im Response.
|
// stattdessen liefern wir einen Hinweis im Response.
|
||||||
func (h *ClusterHandler) RenewSelf(c *gin.Context) {
|
func (h *ClusterHandler) RenewSelf(c *gin.Context) {
|
||||||
if !h.TLSStore.HasCA() {
|
if !h.TLSStore.HasCA() {
|
||||||
@@ -857,16 +857,16 @@ func (h *ClusterHandler) RenewSelf(c *gin.Context) {
|
|||||||
// die wir wirklich brauchen — sonst kann ein joining Peer beliebige
|
// die wir wirklich brauchen — sonst kann ein joining Peer beliebige
|
||||||
// ha_nodes-Felder überschreiben.
|
// ha_nodes-Felder überschreiben.
|
||||||
type registerPeerRequest struct {
|
type registerPeerRequest struct {
|
||||||
ID string `json:"id"` // Joiner's eigene node-id
|
ID string `json:"id"` // Joiner's eigene node-id
|
||||||
Name string `json:"name"` // hostname
|
Name string `json:"name"` // hostname
|
||||||
FQDN string `json:"fqdn"` // sollte mit Client-Cert-CN matchen
|
FQDN string `json:"fqdn"` // sollte mit Client-Cert-CN matchen
|
||||||
APIURL string `json:"api_url"` // https://<fqdn>
|
APIURL string `json:"api_url"` // https://<fqdn>
|
||||||
PublicIP string `json:"public_ip"` // optional
|
PublicIP string `json:"public_ip"` // optional
|
||||||
InternalIP string `json:"internal_ip"` // mTLS-Listener-IP (für peer_ipv4-Set)
|
InternalIP string `json:"internal_ip"` // mTLS-Listener-IP (für peer_ipv4-Set)
|
||||||
MgmtIP string `json:"mgmt_ip"` // optional
|
MgmtIP string `json:"mgmt_ip"` // optional
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
ConfigHash *string `json:"config_hash"` // nil=absent (don't change), ""=no user config
|
ConfigHash *string `json:"config_hash"` // nil=absent (don't change), ""=no user config
|
||||||
Role string `json:"role"` // "" → "peer" (joining peer); "primary" beim Push des Primary
|
Role string `json:"role"` // "" → "peer" (joining peer); "primary" beim Push des Primary
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgentRegisterPeer: vom Joiner nach issue-cert via mTLS aufgerufen.
|
// AgentRegisterPeer: vom Joiner nach issue-cert via mTLS aufgerufen.
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func (h *ClusterHandler) runRollingUpdate(secondary *models.HANode) {
|
|||||||
slog.Info("rolling-update: secondary already at target — skipping secondary step",
|
slog.Info("rolling-update: secondary already at target — skipping secondary step",
|
||||||
"version", candidate)
|
"version", candidate)
|
||||||
} else {
|
} else {
|
||||||
// 1. Secondary triggern
|
// 1. Secondary triggering
|
||||||
slog.Info("rolling-update: posting trigger-update to secondary", "fqdn", secondary.FQDN)
|
slog.Info("rolling-update: posting trigger-update to secondary", "fqdn", secondary.FQDN)
|
||||||
result := h.Aggregator.PostPeer(ctx, *secondary, "/agent/cluster/trigger-update")
|
result := h.Aggregator.PostPeer(ctx, *secondary, "/agent/cluster/trigger-update")
|
||||||
if !result.OK {
|
if !result.OK {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
// FirewallHandler exposes everything under /api/v1/firewall/*:
|
// FirewallHandler exposes everything under /api/v1/firewall/*:
|
||||||
//
|
//
|
||||||
// address-objects — primitive Adress-Definitionen (host/network/range/fqdn)
|
// address-objects — primitive Address-Definitionen (host/network/range/fqdn)
|
||||||
// address-groups — Gruppen von address-objects (mit /members ops)
|
// address-groups — Gruppen von address-objects (mit /members ops)
|
||||||
// services — proto+port (Builtins lassen sich nicht editieren)
|
// services — proto+port (Builtins lassen sich nicht editieren)
|
||||||
// service-groups — Gruppen von services
|
// service-groups — Gruppen von services
|
||||||
@@ -271,7 +271,7 @@ func zoneNamePattern(s string) bool {
|
|||||||
if s == "" || len(s) > 32 {
|
if s == "" || len(s) > 32 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if !(s[0] >= 'a' && s[0] <= 'z') {
|
if s[0] < 'a' || s[0] > 'z' {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i := 1; i < len(s); i++ {
|
for i := 1; i < len(s); i++ {
|
||||||
@@ -352,7 +352,8 @@ func (h *FirewallHandler) CreateAddrObj(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.create", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.create", req.Name, out, h.NodeID)
|
||||||
response.Created(c, out); h.reload(c.Request.Context(), "create")
|
response.Created(c, out)
|
||||||
|
h.reload(c.Request.Context(), "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) UpdateAddrObj(c *gin.Context) {
|
func (h *FirewallHandler) UpdateAddrObj(c *gin.Context) {
|
||||||
@@ -379,7 +380,8 @@ func (h *FirewallHandler) UpdateAddrObj(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.update", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.update", req.Name, out, h.NodeID)
|
||||||
response.OK(c, out); h.reload(c.Request.Context(), "update")
|
response.OK(c, out)
|
||||||
|
h.reload(c.Request.Context(), "update")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) DeleteAddrObj(c *gin.Context) {
|
func (h *FirewallHandler) DeleteAddrObj(c *gin.Context) {
|
||||||
@@ -397,7 +399,8 @@ func (h *FirewallHandler) DeleteAddrObj(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
response.NoContent(c); h.reload(c.Request.Context(), "delete")
|
response.NoContent(c)
|
||||||
|
h.reload(c.Request.Context(), "delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Address Groups ─────────────────────────────────────────────────────
|
// ── Address Groups ─────────────────────────────────────────────────────
|
||||||
@@ -440,7 +443,8 @@ func (h *FirewallHandler) CreateAddrGrp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.create", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.create", req.Name, out, h.NodeID)
|
||||||
response.Created(c, out); h.reload(c.Request.Context(), "create")
|
response.Created(c, out)
|
||||||
|
h.reload(c.Request.Context(), "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) UpdateAddrGrp(c *gin.Context) {
|
func (h *FirewallHandler) UpdateAddrGrp(c *gin.Context) {
|
||||||
@@ -463,7 +467,8 @@ func (h *FirewallHandler) UpdateAddrGrp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.update", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.update", req.Name, out, h.NodeID)
|
||||||
response.OK(c, out); h.reload(c.Request.Context(), "update")
|
response.OK(c, out)
|
||||||
|
h.reload(c.Request.Context(), "update")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) DeleteAddrGrp(c *gin.Context) {
|
func (h *FirewallHandler) DeleteAddrGrp(c *gin.Context) {
|
||||||
@@ -481,7 +486,8 @@ func (h *FirewallHandler) DeleteAddrGrp(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
response.NoContent(c); h.reload(c.Request.Context(), "delete")
|
response.NoContent(c)
|
||||||
|
h.reload(c.Request.Context(), "delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Services ───────────────────────────────────────────────────────────
|
// ── Services ───────────────────────────────────────────────────────────
|
||||||
@@ -524,7 +530,8 @@ func (h *FirewallHandler) CreateService(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.create", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.create", req.Name, out, h.NodeID)
|
||||||
response.Created(c, out); h.reload(c.Request.Context(), "create")
|
response.Created(c, out)
|
||||||
|
h.reload(c.Request.Context(), "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) UpdateService(c *gin.Context) {
|
func (h *FirewallHandler) UpdateService(c *gin.Context) {
|
||||||
@@ -547,7 +554,8 @@ func (h *FirewallHandler) UpdateService(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.update", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.update", req.Name, out, h.NodeID)
|
||||||
response.OK(c, out); h.reload(c.Request.Context(), "update")
|
response.OK(c, out)
|
||||||
|
h.reload(c.Request.Context(), "update")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) DeleteService(c *gin.Context) {
|
func (h *FirewallHandler) DeleteService(c *gin.Context) {
|
||||||
@@ -565,7 +573,8 @@ func (h *FirewallHandler) DeleteService(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
response.NoContent(c); h.reload(c.Request.Context(), "delete")
|
response.NoContent(c)
|
||||||
|
h.reload(c.Request.Context(), "delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Service Groups ─────────────────────────────────────────────────────
|
// ── Service Groups ─────────────────────────────────────────────────────
|
||||||
@@ -608,7 +617,8 @@ func (h *FirewallHandler) CreateSvcGrp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.create", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.create", req.Name, out, h.NodeID)
|
||||||
response.Created(c, out); h.reload(c.Request.Context(), "create")
|
response.Created(c, out)
|
||||||
|
h.reload(c.Request.Context(), "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) UpdateSvcGrp(c *gin.Context) {
|
func (h *FirewallHandler) UpdateSvcGrp(c *gin.Context) {
|
||||||
@@ -631,7 +641,8 @@ func (h *FirewallHandler) UpdateSvcGrp(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.update", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.update", req.Name, out, h.NodeID)
|
||||||
response.OK(c, out); h.reload(c.Request.Context(), "update")
|
response.OK(c, out)
|
||||||
|
h.reload(c.Request.Context(), "update")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) DeleteSvcGrp(c *gin.Context) {
|
func (h *FirewallHandler) DeleteSvcGrp(c *gin.Context) {
|
||||||
@@ -649,7 +660,8 @@ func (h *FirewallHandler) DeleteSvcGrp(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
response.NoContent(c); h.reload(c.Request.Context(), "delete")
|
response.NoContent(c)
|
||||||
|
h.reload(c.Request.Context(), "delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Rules ──────────────────────────────────────────────────────────────
|
// ── Rules ──────────────────────────────────────────────────────────────
|
||||||
@@ -704,7 +716,8 @@ func (h *FirewallHandler) CreateRule(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.create", strconv.FormatInt(out.ID, 10), out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.create", strconv.FormatInt(out.ID, 10), out, h.NodeID)
|
||||||
response.Created(c, out); h.reload(c.Request.Context(), "create")
|
response.Created(c, out)
|
||||||
|
h.reload(c.Request.Context(), "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) UpdateRule(c *gin.Context) {
|
func (h *FirewallHandler) UpdateRule(c *gin.Context) {
|
||||||
@@ -739,7 +752,8 @@ func (h *FirewallHandler) UpdateRule(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.update", strconv.FormatInt(id, 10), out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.update", strconv.FormatInt(id, 10), out, h.NodeID)
|
||||||
response.OK(c, out); h.reload(c.Request.Context(), "update")
|
response.OK(c, out)
|
||||||
|
h.reload(c.Request.Context(), "update")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) DeleteRule(c *gin.Context) {
|
func (h *FirewallHandler) DeleteRule(c *gin.Context) {
|
||||||
@@ -757,7 +771,8 @@ func (h *FirewallHandler) DeleteRule(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
response.NoContent(c); h.reload(c.Request.Context(), "delete")
|
response.NoContent(c)
|
||||||
|
h.reload(c.Request.Context(), "delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) PatchRule(c *gin.Context) {
|
func (h *FirewallHandler) PatchRule(c *gin.Context) {
|
||||||
@@ -850,7 +865,8 @@ func (h *FirewallHandler) CreateNAT(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.create", strconv.FormatInt(out.ID, 10), out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.create", strconv.FormatInt(out.ID, 10), out, h.NodeID)
|
||||||
response.Created(c, out); h.reload(c.Request.Context(), "create")
|
response.Created(c, out)
|
||||||
|
h.reload(c.Request.Context(), "create")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) UpdateNAT(c *gin.Context) {
|
func (h *FirewallHandler) UpdateNAT(c *gin.Context) {
|
||||||
@@ -881,7 +897,8 @@ func (h *FirewallHandler) UpdateNAT(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.update", strconv.FormatInt(id, 10), out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.update", strconv.FormatInt(id, 10), out, h.NodeID)
|
||||||
response.OK(c, out); h.reload(c.Request.Context(), "update")
|
response.OK(c, out)
|
||||||
|
h.reload(c.Request.Context(), "update")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) DeleteNAT(c *gin.Context) {
|
func (h *FirewallHandler) DeleteNAT(c *gin.Context) {
|
||||||
@@ -899,7 +916,8 @@ func (h *FirewallHandler) DeleteNAT(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
response.NoContent(c); h.reload(c.Request.Context(), "delete")
|
response.NoContent(c)
|
||||||
|
h.reload(c.Request.Context(), "delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *FirewallHandler) PatchNAT(c *gin.Context) {
|
func (h *FirewallHandler) PatchNAT(c *gin.Context) {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ func (h *SystemHandler) Register(rg *gin.RouterGroup) {
|
|||||||
// Ergebnis für /cluster/system/load.
|
// Ergebnis für /cluster/system/load.
|
||||||
//
|
//
|
||||||
// Bewusst KEINE Mutations + KEIN /package-versions (würde apt-get update
|
// Bewusst KEINE Mutations + KEIN /package-versions (würde apt-get update
|
||||||
// auf jedem Peer triggern), KEIN /upgrade.
|
// auf jedem Peer triggering), KEIN /upgrade.
|
||||||
func (h *SystemHandler) RegisterAgent(rg *gin.RouterGroup) {
|
func (h *SystemHandler) RegisterAgent(rg *gin.RouterGroup) {
|
||||||
g := rg.Group("/agent/system")
|
g := rg.Group("/agent/system")
|
||||||
g.GET("/health", h.Health)
|
g.GET("/health", h.Health)
|
||||||
@@ -206,7 +206,7 @@ type serviceStatus struct {
|
|||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Unit string `json:"unit"`
|
Unit string `json:"unit"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
State string `json:"state"` // active|inactive|failed|activating|...
|
State string `json:"state"` // active|inactive|failed|activating|...
|
||||||
Since string `json:"since,omitempty"` // ActiveEnterTimestamp
|
Since string `json:"since,omitempty"` // ActiveEnterTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +378,7 @@ func (h *SystemHandler) Maintenance(c *gin.Context) {
|
|||||||
func (h *SystemHandler) ToggleMaintenance(c *gin.Context) {
|
func (h *SystemHandler) ToggleMaintenance(c *gin.Context) {
|
||||||
if h.Setup == nil {
|
if h.Setup == nil {
|
||||||
response.Err(c, http.StatusServiceUnavailable,
|
response.Err(c, http.StatusServiceUnavailable,
|
||||||
simpleErr("setup not initialised"))
|
simpleErr("setup not initialized"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var req struct {
|
var req struct {
|
||||||
@@ -430,7 +430,7 @@ func (h *SystemHandler) BackupRetention(c *gin.Context) {
|
|||||||
// keep=0 → wieder Default, keep=1..365 → custom.
|
// keep=0 → wieder Default, keep=1..365 → custom.
|
||||||
func (h *SystemHandler) SetBackupRetention(c *gin.Context) {
|
func (h *SystemHandler) SetBackupRetention(c *gin.Context) {
|
||||||
if h.Setup == nil {
|
if h.Setup == nil {
|
||||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialised"))
|
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialized"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var req struct {
|
var req struct {
|
||||||
@@ -466,7 +466,7 @@ func (h *SystemHandler) AuditRetention(c *gin.Context) {
|
|||||||
// SetAuditRetention setzt Audit-Retention in Tagen. 0..3650.
|
// SetAuditRetention setzt Audit-Retention in Tagen. 0..3650.
|
||||||
func (h *SystemHandler) SetAuditRetention(c *gin.Context) {
|
func (h *SystemHandler) SetAuditRetention(c *gin.Context) {
|
||||||
if h.Setup == nil {
|
if h.Setup == nil {
|
||||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialised"))
|
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialized"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var req struct {
|
var req struct {
|
||||||
@@ -513,7 +513,7 @@ func (h *SystemHandler) IPv6(c *gin.Context) {
|
|||||||
|
|
||||||
func (h *SystemHandler) SetIPv6(c *gin.Context) {
|
func (h *SystemHandler) SetIPv6(c *gin.Context) {
|
||||||
if h.Setup == nil {
|
if h.Setup == nil {
|
||||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialised"))
|
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialized"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var req struct {
|
var req struct {
|
||||||
@@ -582,7 +582,7 @@ LIMIT 10`)
|
|||||||
// HAProxyReload zwingt ein systemctl reload haproxy.service — nützlich
|
// HAProxyReload zwingt ein systemctl reload haproxy.service — nützlich
|
||||||
// wenn der Operator manuell in /etc/edgeguard/tls/ geschrieben hat
|
// wenn der Operator manuell in /etc/edgeguard/tls/ geschrieben hat
|
||||||
// (z. B. eigenes PEM per SSH kopiert) und HAProxy das neue Cert sehen
|
// (z. B. eigenes PEM per SSH kopiert) und HAProxy das neue Cert sehen
|
||||||
// soll, ohne eine UI-Mutation zu triggern die das automatisch täte.
|
// soll, ohne eine UI-Mutation zu triggering die das automatisch täte.
|
||||||
func (h *SystemHandler) HAProxyReload(c *gin.Context) {
|
func (h *SystemHandler) HAProxyReload(c *gin.Context) {
|
||||||
out, err := exec.Command("sudo", "-n", "/usr/bin/systemctl", "reload", "haproxy.service").CombinedOutput()
|
out, err := exec.Command("sudo", "-n", "/usr/bin/systemctl", "reload", "haproxy.service").CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -600,12 +600,12 @@ func (h *SystemHandler) HAProxyReload(c *gin.Context) {
|
|||||||
// starten darf. edgeguard-api selbst ist bewusst ausgeschlossen (würde
|
// starten darf. edgeguard-api selbst ist bewusst ausgeschlossen (würde
|
||||||
// die eigene HTTP-Response killen). postgresql ebenfalls (Datenpfad).
|
// die eigene HTTP-Response killen). postgresql ebenfalls (Datenpfad).
|
||||||
var restartAllowlist = map[string]bool{
|
var restartAllowlist = map[string]bool{
|
||||||
"haproxy": true,
|
"haproxy": true,
|
||||||
"squid": true,
|
"squid": true,
|
||||||
"unbound": true,
|
"unbound": true,
|
||||||
"chrony": true,
|
"chrony": true,
|
||||||
"nftables": true,
|
"nftables": true,
|
||||||
"wireguard": true, // wireguard als Metadienst; einzelne wg-Ifaces über wg-quick@<name>
|
"wireguard": true, // wireguard als Metadienst; einzelne wg-Ifaces über wg-quick@<name>
|
||||||
"edgeguard-scheduler": true,
|
"edgeguard-scheduler": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,7 +920,7 @@ func (h *SystemHandler) Upgrade(c *gin.Context) {
|
|||||||
// beiden Namespaces aus zugänglich.
|
// beiden Namespaces aus zugänglich.
|
||||||
const scriptPath = "/var/lib/edgeguard/upgrade.sh"
|
const scriptPath = "/var/lib/edgeguard/upgrade.sh"
|
||||||
// Retry-Logik gegen Gitea-Packages.gz-Race: nach einem frischen
|
// Retry-Logik gegen Gitea-Packages.gz-Race: nach einem frischen
|
||||||
// Publish kann der Packages-Index für ein paar Sekunden inkonsistent
|
// Publish kann der Packages-Index für ein paar Sekunden inconsistent
|
||||||
// sein (z. B. Meta uploaded, api/ui noch nicht in der regenerierten
|
// sein (z. B. Meta uploaded, api/ui noch nicht in der regenerierten
|
||||||
// Index-Datei) → apt-resolver-fail mit "no choices are installable".
|
// Index-Datei) → apt-resolver-fail mit "no choices are installable".
|
||||||
// Drei Versuche mit 15s/30s Backoff geben Gitea Zeit den Index
|
// Drei Versuche mit 15s/30s Backoff geben Gitea Zeit den Index
|
||||||
@@ -1235,4 +1235,3 @@ func flagsToList(f net.Flags) []string {
|
|||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Package kea renders the Kea DHCPv4 server config from the dhcp_*
|
// Package kea renders the Kea DHCPv4 server config from the dhcp_*
|
||||||
// tables and manages the kea-dhcp4-server service lifecycle.
|
// tables and manages the kea-dhcp4-server service lifecycle.
|
||||||
//
|
//
|
||||||
// The config is built as a Go struct and json-marshalled (NOT a text
|
// The config is built as a Go struct and json-marshaled (NOT a text
|
||||||
// template) so the output is always syntactically valid JSON. Managed
|
// template) so the output is always syntactically valid JSON. Managed
|
||||||
// at /etc/edgeguard/kea/kea-dhcp4.conf (edgeguard-owned); postinst
|
// at /etc/edgeguard/kea/kea-dhcp4.conf (edgeguard-owned); postinst
|
||||||
// symlinks /etc/kea/kea-dhcp4.conf to it.
|
// symlinks /etc/kea/kea-dhcp4.conf to it.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func TestTemplateNopreemptOnBothInstances(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GARP muss forciert + periodisch aufgefrischt werden, sonst altert die
|
// GARP muss forciert + periodic aufgefrischt werden, sonst altert die
|
||||||
// VIP-MAC am Upstream-Switch und die Failover-IP wird unerreichbar.
|
// VIP-MAC am Upstream-Switch und die Failover-IP wird unerreichbar.
|
||||||
func TestTemplateGARPRefresh(t *testing.T) {
|
func TestTemplateGARPRefresh(t *testing.T) {
|
||||||
out := render(t, testView())
|
out := render(t, testView())
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package models
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ClusterSettings ist die Singleton-Tabelle (id=1) für VIP/VRRP-
|
// ClusterSettings ist die Singleton-Tabelle (id=1) für VIP/VRRP-
|
||||||
// und Replikations-Konfiguration. Angelegt in Migration 0029.
|
// und Replikations-Configuration. Angelegt in Migration 0029.
|
||||||
// hb_* = zweite VRRP-Instanz für Split-Brain-Schutz (0033).
|
// hb_* = zweite VRRP-Instanz für Split-Brain-Schutz (0033).
|
||||||
// gw_check_ip = Gateway-IP für vrrp_script chk_gateway (0033).
|
// gw_check_ip = Gateway-IP für vrrp_script chk_gateway (0033).
|
||||||
type ClusterSettings struct {
|
type ClusterSettings struct {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package models
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// DHCPSettings ist die node-lokale Singleton-Konfiguration des Kea-DHCPv4-
|
// DHCPSettings ist die node-lokale Singleton-Configuration des Kea-DHCPv4-
|
||||||
// Servers (ob diese Node DHCP betreibt + globale Defaults).
|
// Servers (ob diese Node DHCP betreibt + globale Defaults).
|
||||||
type DHCPSettings struct {
|
type DHCPSettings struct {
|
||||||
ID int `gorm:"column:id;primaryKey" json:"id"`
|
ID int `gorm:"column:id;primaryKey" json:"id"`
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package models
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// OIDCSettings ist die Singleton-Konfiguration für OIDC/Keycloak-SSO.
|
// OIDCSettings ist die Singleton-Configuration für OIDC/Keycloak-SSO.
|
||||||
// ClientSecretEnc trägt den verschlüsselten Client-Secret (secrets.Box)
|
// ClientSecretEnc trägt den verschlüsselten Client-Secret (secrets.Box)
|
||||||
// und wird NIE serialisiert (json:"-").
|
// und wird NIE serialisiert (json:"-").
|
||||||
type OIDCSettings struct {
|
type OIDCSettings struct {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package models
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// RADIUSSettings ist die node-lokale Singleton-Konfiguration des
|
// RADIUSSettings ist die node-lokale Singleton-Configuration des
|
||||||
// FreeRADIUS-Servers (ob diese Node RADIUS betreibt + Listen-Adressen).
|
// FreeRADIUS-Servers (ob diese Node RADIUS betreibt + Listen-Adressen).
|
||||||
type RADIUSSettings struct {
|
type RADIUSSettings struct {
|
||||||
ID int `gorm:"column:id;primaryKey" json:"id"`
|
ID int `gorm:"column:id;primaryKey" json:"id"`
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type WireguardInterface struct {
|
|||||||
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
|
||||||
|
|
||||||
// PrivateKeyEnc / PeerPSKEnc are loaded from the DB as raw bytes
|
// PrivateKeyEnc / PeerPSKEnc are loaded from the DB as raw bytes
|
||||||
// — handler never serialises them. JSON tag uses '-' so they
|
// — handler never serializes them. JSON tag uses '-' so they
|
||||||
// don't leak into responses if a developer accidentally returns
|
// don't leak into responses if a developer accidentally returns
|
||||||
// the model directly.
|
// the model directly.
|
||||||
PrivateKeyEnc []byte `gorm:"column:private_key_enc" json:"-"`
|
PrivateKeyEnc []byte `gorm:"column:private_key_enc" json:"-"`
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func refreshInternal(ctx context.Context, force bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartBackgroundRefresh: Fire-and-Forget-Goroutine die einmal beim
|
// StartBackgroundRefresh: Fire-and-Forget-Goroutine die einmal beim
|
||||||
// Start + dann periodisch apt-get update fährt, damit der Apt-Cache
|
// Start + dann periodic apt-get update fährt, damit der Apt-Cache
|
||||||
// auch ohne UI-Traffic frisch bleibt. So zeigt der Banner kurz nach
|
// auch ohne UI-Traffic frisch bleibt. So zeigt der Banner kurz nach
|
||||||
// `make publish` ein verfügbares Update, statt 5 min auf die nächste
|
// `make publish` ein verfügbares Update, statt 5 min auf die nächste
|
||||||
// UI-Poll-Welle zu warten.
|
// UI-Poll-Welle zu warten.
|
||||||
|
|||||||
@@ -63,9 +63,9 @@ func Run(ctx context.Context, gens []configgen.Generator, only []string) ([]Resu
|
|||||||
return out, errors.Join(errs...)
|
return out, errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summarise turns the result slice into a human-readable multiline
|
// Summarize turns the result slice into a human-readable multiline
|
||||||
// string. Used by `edgeguard-ctl render-config` to print to stdout.
|
// string. Used by `edgeguard-ctl render-config` to print to stdout.
|
||||||
func Summarise(results []Result) string {
|
func Summarize(results []Result) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
if r.Skipped {
|
if r.Skipped {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type Result struct {
|
|||||||
Took time.Duration `json:"-"`
|
Took time.Duration `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// validTarget ist eine konservative Erlaubnis: Buchstaben, Ziffern,
|
// validTarget ist eine conservative Erlaubnis: Buchstaben, Ziffern,
|
||||||
// Punkt, Doppelpunkt (IPv6), Schrägstrich (Pfade in curl-URLs), Bindestrich,
|
// Punkt, Doppelpunkt (IPv6), Schrägstrich (Pfade in curl-URLs), Bindestrich,
|
||||||
// Unterstrich. Whitespace, $, `, `;`, `&`, `|`, `>` etc. werden gesperrt.
|
// Unterstrich. Whitespace, $, `, `;`, `&`, `|`, `>` etc. werden gesperrt.
|
||||||
func validTarget(s string, max int) error {
|
func validTarget(s string, max int) error {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ type Claims struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Authenticator ist der testbare Seam: Aufbau der Auth-URL und der
|
// Authenticator ist der testbare Seam: Aufbau der Auth-URL und der
|
||||||
// Code-Exchange inkl. ID-Token-Verifikation + Claim-Extraktion. Der
|
// Code-Exchange inkl. ID-Token-Verification + Claim-Extraktion. Der
|
||||||
// Handler hängt nur hieran, sodass Tests einen Fake injizieren können.
|
// Handler hängt nur hieran, sodass Tests einen Fake injizieren können.
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
// AuthCodeURL baut die Redirect-URL zum IdP (state + nonce + PKCE-Challenge).
|
// AuthCodeURL baut die Redirect-URL zum IdP (state + nonce + PKCE-Challenge).
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Package oidc kapselt die OIDC/Keycloak-SSO-Konfiguration (Singleton-
|
// Package oidc kapselt die OIDC/Keycloak-SSO-Configuration (Singleton-
|
||||||
// Settings + verschlüsseltes Client-Secret) und einen lazy aufgebauten
|
// Settings + verschlüsseltes Client-Secret) und einen lazy aufgebauten
|
||||||
// OIDC-Provider/Verifier. Login-Flow-State ist stateless (signiertes
|
// OIDC-Provider/Verifier. Login-Flow-State ist stateless (signiertes
|
||||||
// Cookie im Handler), daher hält dieses Paket keinen Request-State.
|
// Cookie im Handler), daher hält dieses Paket keinen Request-State.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const masterKeyLen = 32
|
|||||||
const DefaultMasterKeyPath = "/var/lib/edgeguard/.master_key"
|
const DefaultMasterKeyPath = "/var/lib/edgeguard/.master_key"
|
||||||
|
|
||||||
// Box uses AES-256-GCM with a static master key to seal/unseal
|
// Box uses AES-256-GCM with a static master key to seal/unseal
|
||||||
// values. Concurrency-safe; the cipher is initialised once.
|
// values. Concurrency-safe; the cipher is initialized once.
|
||||||
type Box struct {
|
type Box struct {
|
||||||
once sync.Once
|
once sync.Once
|
||||||
aead cipher.AEAD
|
aead cipher.AEAD
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/secrets"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImportResult summarises what an Import call did so the CLI can
|
// ImportResult summarizes what an Import call did so the CLI can
|
||||||
// report it back to the operator.
|
// report it back to the operator.
|
||||||
type ImportResult struct {
|
type ImportResult struct {
|
||||||
IfacesAdded int `json:"ifaces_added"`
|
IfacesAdded int `json:"ifaces_added"`
|
||||||
PeersAdded int `json:"peers_added"`
|
PeersAdded int `json:"peers_added"`
|
||||||
Skipped []string `json:"skipped,omitempty"` // ifaces already present, with reason
|
Skipped []string `json:"skipped,omitempty"` // ifaces already present, with reason
|
||||||
}
|
}
|
||||||
|
|
||||||
// Importer takes existing /etc/wireguard/*.conf files and translates
|
// Importer takes existing /etc/wireguard/*.conf files and translates
|
||||||
|
|||||||
@@ -36,10 +36,10 @@ func buildDirectives(cfg models.WafConfig, crsDir string) string {
|
|||||||
|
|
||||||
sb.WriteString("SecRequestBodyAccess On\n")
|
sb.WriteString("SecRequestBodyAccess On\n")
|
||||||
sb.WriteString("SecResponseBodyAccess Off\n")
|
sb.WriteString("SecResponseBodyAccess Off\n")
|
||||||
sb.WriteString("SecRequestBodyLimit 13107200\n") // 12.5 MB
|
sb.WriteString("SecRequestBodyLimit 13107200\n") // 12.5 MB
|
||||||
sb.WriteString("SecRequestBodyInMemoryLimit 131072\n") // 128 KB
|
sb.WriteString("SecRequestBodyInMemoryLimit 131072\n") // 128 KB
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("SecRuleEngine %s\n", ruleEngineMode(cfg.Mode)))
|
fmt.Fprintf(&sb, "SecRuleEngine %s\n", ruleEngineMode(cfg.Mode))
|
||||||
|
|
||||||
if crsDir != "" && crsAvailable(crsDir) {
|
if crsDir != "" && crsAvailable(crsDir) {
|
||||||
// Paranoia level MUST be set before CRS rules are included.
|
// Paranoia level MUST be set before CRS rules are included.
|
||||||
@@ -47,22 +47,20 @@ func buildDirectives(cfg models.WafConfig, crsDir string) string {
|
|||||||
if pl < 1 || pl > 4 {
|
if pl < 1 || pl > 4 {
|
||||||
pl = 1
|
pl = 1
|
||||||
}
|
}
|
||||||
sb.WriteString(fmt.Sprintf(
|
fmt.Fprintf(&sb, "SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=%d\"\n", pl)
|
||||||
"SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=%d\"\n", pl,
|
|
||||||
))
|
|
||||||
setupConf := filepath.Join(crsDir, "crs-setup.conf")
|
setupConf := filepath.Join(crsDir, "crs-setup.conf")
|
||||||
if _, err := os.Stat(setupConf); err == nil {
|
if _, err := os.Stat(setupConf); err == nil {
|
||||||
sb.WriteString(fmt.Sprintf("Include %s\n", setupConf))
|
fmt.Fprintf(&sb, "Include %s\n", setupConf)
|
||||||
}
|
}
|
||||||
rulesGlob := filepath.Join(crsDir, "rules", "*.conf")
|
rulesGlob := filepath.Join(crsDir, "rules", "*.conf")
|
||||||
sb.WriteString(fmt.Sprintf("Include %s\n", rulesGlob))
|
fmt.Fprintf(&sb, "Include %s\n", rulesGlob)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rule exclusions (applied after CRS load so they override CRS).
|
// Rule exclusions (applied after CRS load so they override CRS).
|
||||||
for _, id := range cfg.RuleExclusions {
|
for _, id := range cfg.RuleExclusions {
|
||||||
id = strings.TrimSpace(id)
|
id = strings.TrimSpace(id)
|
||||||
if id != "" {
|
if id != "" {
|
||||||
sb.WriteString(fmt.Sprintf("SecRuleRemoveById %s\n", id))
|
fmt.Fprintf(&sb, "SecRuleRemoveById %s\n", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import (
|
|||||||
// SPOEAgent wraps the haproxy-go SPOE server and dispatches each
|
// SPOEAgent wraps the haproxy-go SPOE server and dispatches each
|
||||||
// inspected request to the appropriate per-domain Coraza engine.
|
// inspected request to the appropriate per-domain Coraza engine.
|
||||||
type SPOEAgent struct {
|
type SPOEAgent struct {
|
||||||
Manager *Manager
|
Manager *Manager
|
||||||
AlertWriter *AlertWriter
|
AlertWriter *AlertWriter
|
||||||
Addr string
|
Addr string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListenAndServe starts the SPOE agent. Blocks until ctx is cancelled.
|
// ListenAndServe starts the SPOE agent. Blocks until ctx is canceled.
|
||||||
func (a *SPOEAgent) ListenAndServe(ctx context.Context) error {
|
func (a *SPOEAgent) ListenAndServe(ctx context.Context) error {
|
||||||
agent := spop.Agent{
|
agent := spop.Agent{
|
||||||
Addr: a.Addr,
|
Addr: a.Addr,
|
||||||
|
|||||||
Reference in New Issue
Block a user