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/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/clustertls"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster/jointoken"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
||||
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"
|
||||
"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/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/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/backends"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backendservers"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/backup"
|
||||
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"
|
||||
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/domains"
|
||||
"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/syslogs"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/forwardproxy"
|
||||
"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"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
@@ -287,7 +287,7 @@ func main() {
|
||||
// reload haproxy. Wird in Domains/Backends/RoutingRules-Handler
|
||||
// injiziert, damit jede Änderung ohne expliziten render-config-
|
||||
// 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
|
||||
// nachdem haproxyReloader-closure existiert.
|
||||
haproxyReloaderForLater := func(ctx context.Context) error {
|
||||
@@ -296,18 +296,18 @@ func main() {
|
||||
systemHdl.WithMaintenance(setupStore, haproxyReloaderForLater)
|
||||
|
||||
// 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.
|
||||
systemHdl.WithAudit(auditRepo, nodeID)
|
||||
systemHdl.WithDB(pool)
|
||||
systemHdl.WithConfigPreviewers(map[string]func(context.Context) (string, error){
|
||||
"haproxy": haproxy.New(pool).RenderToString,
|
||||
"nftables": firewallrender.New(pool).RenderToString,
|
||||
"squid": squidrender.New(pool).RenderToString,
|
||||
"unbound": unboundrender.New(pool).RenderToString,
|
||||
"chrony": chronyrender.New(pool).RenderToString,
|
||||
"wireguard": wgrender.New(pool, secretsBox).RenderToString,
|
||||
})
|
||||
"haproxy": haproxy.New(pool).RenderToString,
|
||||
"nftables": firewallrender.New(pool).RenderToString,
|
||||
"squid": squidrender.New(pool).RenderToString,
|
||||
"unbound": unboundrender.New(pool).RenderToString,
|
||||
"chrony": chronyrender.New(pool).RenderToString,
|
||||
"wireguard": wgrender.New(pool, secretsBox).RenderToString,
|
||||
})
|
||||
setupHdl.WithAudit(auditRepo, nodeID)
|
||||
setupHdl.WithClusterSupport(clusterStore, func(ctx context.Context) error {
|
||||
return firewallrender.New(pool).Render(ctx)
|
||||
@@ -411,7 +411,7 @@ func main() {
|
||||
// services whose state feeds the auto-FW-rule generator (DNS
|
||||
// listen-IPs, Squid ACL count, WG listen-port, NTP serve-clients).
|
||||
// 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 {
|
||||
return func(ctx context.Context) error {
|
||||
if err := svc(ctx); err != nil {
|
||||
@@ -449,14 +449,14 @@ func main() {
|
||||
handlers.NewForwardProxyHandler(fwdProxyRepo, auditRepo, nodeID, withFW(squidReloader)).Register(authed)
|
||||
|
||||
// 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 {
|
||||
return unboundrender.New(pool).Render(ctx)
|
||||
}
|
||||
handlers.NewDNSHandler(dnsRepo, auditRepo, nodeID, withFW(unboundReloader)).Register(authed)
|
||||
|
||||
// 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 {
|
||||
return chronyrender.New(pool).Render(ctx)
|
||||
}
|
||||
@@ -502,7 +502,7 @@ func main() {
|
||||
// Startup-Render nftables: stellt sicher dass Template-Änderungen
|
||||
// aus einem Update (z.B. neue WireGuard forward-Chain-Auto-Regel)
|
||||
// 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.
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
|
||||
@@ -8,13 +8,13 @@ import (
|
||||
"time"
|
||||
|
||||
"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/kea"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster"
|
||||
"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/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/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/services/configorch"
|
||||
"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)
|
||||
fmt.Print(configorch.Summarise(results))
|
||||
fmt.Print(configorch.Summarize(results))
|
||||
if runErr != nil {
|
||||
fmt.Fprintln(os.Stderr, "render-config aborted:", runErr)
|
||||
return 1
|
||||
|
||||
@@ -129,7 +129,7 @@ const (
|
||||
// ntpSyncCheckInterval — alle 10 Minuten chronyc tracking aufrufen.
|
||||
// Keine Sync bedeutet: Uhr driftet → TLS-Cert-Prüfung schlägt fehl
|
||||
// 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
|
||||
// Alert-Regen produziert.
|
||||
ntpSyncCheckInterval = 10 * time.Minute
|
||||
@@ -140,7 +140,7 @@ const (
|
||||
// ist der Tunnel effektiv tot — Traffic droht lautlos. Dedupe 30min
|
||||
// pro Tunnel damit schnell wiederhergestellte Tunnels nur einmal feuern.
|
||||
wgTunnelCheckInterval = 5 * time.Minute
|
||||
wgStaleSec = int64(5 * 60) // 5 Minuten ohne Handshake = tot
|
||||
wgStaleSec = int64(5 * 60) // 5 Minuten ohne Handshake = tot
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -241,7 +241,7 @@ func main() {
|
||||
ntpSyncTick := time.NewTicker(ntpSyncCheckInterval)
|
||||
defer ntpSyncTick.Stop()
|
||||
// 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.
|
||||
|
||||
wgTunnelTick := time.NewTicker(wgTunnelCheckInterval)
|
||||
@@ -682,7 +682,10 @@ func runBackendDownCheck(ctx context.Context, pool *pgxpool.Pool, a *alerts.Serv
|
||||
return
|
||||
}
|
||||
|
||||
type srvEntry struct{ status string; hasCheck bool }
|
||||
type srvEntry struct {
|
||||
status string
|
||||
hasCheck bool
|
||||
}
|
||||
byBackend := map[string][]srvEntry{}
|
||||
colIdx := map[string]int{}
|
||||
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,
|
||||
"Cert-Renewal fehlgeschlagen: "+domain,
|
||||
"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).")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ func main() {
|
||||
slog.Error("waf: SPOE agent stopped", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Graceful shutdown (ctx cancelled): gepufferte Alerts flushen.
|
||||
// Graceful shutdown (ctx canceled): gepufferte Alerts flushen.
|
||||
alertWriter.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ func splitCSV(s string) []string {
|
||||
// filterNonLoopback wirft 127.x / ::1 raus — wenn NUR localhost im
|
||||
// listen_addresses ist, lassen wir den bindaddress-Block weg und
|
||||
// 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 {
|
||||
out := []string{}
|
||||
for _, ip := range in {
|
||||
|
||||
@@ -2,18 +2,19 @@
|
||||
// für Node-to-Node mTLS-Kommunikation.
|
||||
//
|
||||
// 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/peer.crt (0644) — diese Node
|
||||
// /var/lib/edgeguard/cluster-tls/peer.key (0600, edgeguard:edgeguard)
|
||||
//
|
||||
// /var/lib/edgeguard/cluster-tls/ca.crt (0644)
|
||||
// /var/lib/edgeguard/cluster-tls/ca.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:
|
||||
// * 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.
|
||||
// * 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
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
// organisation landet im Subject — typischerweise die FQDN-Domain.
|
||||
func (s *Store) InitCA(organisation string, now func() time.Time) error {
|
||||
// organization landet im Subject — typischerweise die FQDN-Domain.
|
||||
func (s *Store) InitCA(organization string, now func() time.Time) error {
|
||||
if s.HasCA() {
|
||||
return nil
|
||||
}
|
||||
@@ -93,7 +94,7 @@ func (s *Store) InitCA(organisation string, now func() time.Time) error {
|
||||
SerialNumber: serial,
|
||||
Subject: pkix.Name{
|
||||
CommonName: "EdgeGuard Cluster CA",
|
||||
Organization: []string{organisation},
|
||||
Organization: []string{organization},
|
||||
},
|
||||
NotBefore: now().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
|
||||
// kann negativ sein wenn der Cert schon abgelaufen ist.
|
||||
type CertInfo struct {
|
||||
CommonName string `json:"common_name"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
DaysRemaining int `json:"days_remaining"`
|
||||
IsCA bool `json:"is_ca"`
|
||||
SerialHex string `json:"serial_hex"`
|
||||
CommonName string `json:"common_name"`
|
||||
NotBefore time.Time `json:"not_before"`
|
||||
NotAfter time.Time `json:"not_after"`
|
||||
DaysRemaining int `json:"days_remaining"`
|
||||
IsCA bool `json:"is_ca"`
|
||||
SerialHex string `json:"serial_hex"`
|
||||
}
|
||||
|
||||
// PeerCertInfo liefert die Metadata des eigenen peer.crt. Wenn keiner
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package 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_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.
|
||||
// Aufrufer ist normalerweise edgeguard-ctl unter Operator-Privilegien.
|
||||
// Aufrufer ist normalerweise edgeguard-ctl unter Operator-Privilege.
|
||||
func SaveLocalConfig(path string, c *LocalConfig) error {
|
||||
if path == "" {
|
||||
path = DefaultLocalConfigPath
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
//
|
||||
// Name returns a stable identifier ("haproxy", "nftables", …)
|
||||
// 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 {
|
||||
Name() string
|
||||
Render(ctx context.Context) error
|
||||
|
||||
@@ -98,7 +98,7 @@ type HubItem struct {
|
||||
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 {
|
||||
Installed bool `json:"installed"`
|
||||
AgentRunning bool `json:"agent_running"`
|
||||
@@ -362,15 +362,7 @@ func Machines(ctx context.Context) ([]Machine, error) {
|
||||
}
|
||||
result := make([]Machine, len(raw))
|
||||
for i, r := range raw {
|
||||
result[i] = Machine{
|
||||
MachineID: r.MachineID,
|
||||
CreatedAt: r.CreatedAt,
|
||||
UpdatedAt: r.UpdatedAt,
|
||||
LastPush: r.LastPush,
|
||||
IsValidated: r.IsValidated,
|
||||
Version: r.Version,
|
||||
Status: r.Status,
|
||||
}
|
||||
result[i] = Machine(r)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import "testing"
|
||||
// startup, the API restart-looped, the cluster rolling-upgrade hung.
|
||||
//
|
||||
// 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
|
||||
// service start via Migrate() and via `edgeguard-ctl migrate check`
|
||||
// in postinst (defense in depth).
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// + groups, policy rules, nat rules, ha_nodes peer IPs.
|
||||
// 2. Each rule and nat-rule is "resolved" — group references
|
||||
// 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
|
||||
// sets, default-deny baseline, forward + input chains carrying
|
||||
// the resolved rules (priority-sorted), nat prerouting +
|
||||
@@ -150,12 +150,12 @@ type AutoFWRule struct {
|
||||
Proto string
|
||||
Port int
|
||||
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)
|
||||
Comment string
|
||||
}
|
||||
|
||||
// RuleLeg is one materialised nft policy line.
|
||||
// RuleLeg is one materialized nft policy line.
|
||||
type RuleLeg struct {
|
||||
RuleID int64
|
||||
Action string
|
||||
@@ -166,7 +166,7 @@ type RuleLeg struct {
|
||||
DstIfaces []string
|
||||
SrcAddrs []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
|
||||
// Regeln bleibt es "" (familienagnostisch, kein ip/ip6-Match).
|
||||
L3 string
|
||||
@@ -560,7 +560,7 @@ func splitByFamily(exprs []string) (v4, v6 []string) {
|
||||
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 {
|
||||
switch svc.Proto {
|
||||
case "icmp":
|
||||
@@ -608,7 +608,7 @@ func expandFamilyLegs(r ResolvedRule, svc ResolvedService, hasSvc bool) []RuleLe
|
||||
}
|
||||
|
||||
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.
|
||||
return []RuleLeg{base}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func renderView(t *testing.T) string {
|
||||
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: "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{
|
||||
{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) {
|
||||
out := renderView(t)
|
||||
mustContain := []string{
|
||||
"ip saddr { 10.0.0.0/24 }", // v4-Regel unverändert
|
||||
"ip6 saddr { fd00::/64 }", // v6-Regel
|
||||
"ip6 daddr 2001:db8::/64", // v6-DNAT-Match
|
||||
"dnat to [fd00::2]:8080", // v6-DNAT-Target geklammert
|
||||
"dnat to 10.0.0.5:80", // v4-DNAT-Target unverändert
|
||||
"ip saddr { 10.0.0.0/24 }", // v4-Regel unverändert
|
||||
"ip6 saddr { fd00::/64 }", // v6-Regel
|
||||
"ip6 daddr 2001:db8::/64", // v6-DNAT-Match
|
||||
"dnat to [fd00::2]:8080", // v6-DNAT-Target geklammert
|
||||
"dnat to 10.0.0.5:80", // v4-DNAT-Target unverändert
|
||||
"ip6 saddr fd00::/64 snat to 2001:db8::99",
|
||||
`oifname "wg7" ip6 saddr fd00:99::/64 masquerade`,
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ type ClusterHandler struct {
|
||||
Store *cluster.Store
|
||||
LocalID string
|
||||
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
|
||||
// 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}
|
||||
}
|
||||
|
||||
// WithAggregator: optionale Aggregator-Konfiguration. Nur wenn vorhanden
|
||||
// WithAggregator: optionale Aggregator-Configuration. Nur wenn vorhanden
|
||||
// wird /cluster/system/load die Peers via mTLS abklappern.
|
||||
func (h *ClusterHandler) WithAggregator(a *aggregator.Aggregator) *ClusterHandler {
|
||||
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
|
||||
// lokale Node (LocalID) — die kannst du nicht via UI löschen, sonst
|
||||
// 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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// UpdateVIPSettings speichert die VIP/VRRP-Konfiguration und triggert
|
||||
// UpdateVIPSettings speichert die VIP/VRRP-Configuration und triggert
|
||||
// einen Keepalived-Config-Render. Viewer-Schutz via RequireAdminForMutations-
|
||||
// Middleware auf der authed-Group — kein Extra-Check nötig.
|
||||
func (h *ClusterHandler) UpdateVIPSettings(c *gin.Context) {
|
||||
@@ -338,7 +338,7 @@ func (h *ClusterHandler) WithPeerReloader(r PeerReloader) *ClusterHandler {
|
||||
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 {
|
||||
h.Version = v
|
||||
return h
|
||||
@@ -359,7 +359,7 @@ type ClusterStatus struct {
|
||||
LocalID string `json:"local_id"`
|
||||
LocalNode *models.HANode `json:"local_node,omitempty"`
|
||||
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"
|
||||
DriftFound bool `json:"drift_found"`
|
||||
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-
|
||||
// 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.
|
||||
func (h *ClusterHandler) RenewSelf(c *gin.Context) {
|
||||
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
|
||||
// ha_nodes-Felder überschreiben.
|
||||
type registerPeerRequest struct {
|
||||
ID string `json:"id"` // Joiner's eigene node-id
|
||||
Name string `json:"name"` // hostname
|
||||
FQDN string `json:"fqdn"` // sollte mit Client-Cert-CN matchen
|
||||
APIURL string `json:"api_url"` // https://<fqdn>
|
||||
PublicIP string `json:"public_ip"` // optional
|
||||
InternalIP string `json:"internal_ip"` // mTLS-Listener-IP (für peer_ipv4-Set)
|
||||
MgmtIP string `json:"mgmt_ip"` // optional
|
||||
ID string `json:"id"` // Joiner's eigene node-id
|
||||
Name string `json:"name"` // hostname
|
||||
FQDN string `json:"fqdn"` // sollte mit Client-Cert-CN matchen
|
||||
APIURL string `json:"api_url"` // https://<fqdn>
|
||||
PublicIP string `json:"public_ip"` // optional
|
||||
InternalIP string `json:"internal_ip"` // mTLS-Listener-IP (für peer_ipv4-Set)
|
||||
MgmtIP string `json:"mgmt_ip"` // optional
|
||||
Version string `json:"version"`
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// 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",
|
||||
"version", candidate)
|
||||
} else {
|
||||
// 1. Secondary triggern
|
||||
// 1. Secondary triggering
|
||||
slog.Info("rolling-update: posting trigger-update to secondary", "fqdn", secondary.FQDN)
|
||||
result := h.Aggregator.PostPeer(ctx, *secondary, "/agent/cluster/trigger-update")
|
||||
if !result.OK {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
// 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)
|
||||
// services — proto+port (Builtins lassen sich nicht editieren)
|
||||
// service-groups — Gruppen von services
|
||||
@@ -271,7 +271,7 @@ func zoneNamePattern(s string) bool {
|
||||
if s == "" || len(s) > 32 {
|
||||
return false
|
||||
}
|
||||
if !(s[0] >= 'a' && s[0] <= 'z') {
|
||||
if s[0] < 'a' || s[0] > 'z' {
|
||||
return false
|
||||
}
|
||||
for i := 1; i < len(s); i++ {
|
||||
@@ -352,7 +352,8 @@ func (h *FirewallHandler) CreateAddrObj(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -379,7 +380,8 @@ func (h *FirewallHandler) UpdateAddrObj(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -397,7 +399,8 @@ func (h *FirewallHandler) DeleteAddrObj(c *gin.Context) {
|
||||
}
|
||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_obj.delete",
|
||||
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 ─────────────────────────────────────────────────────
|
||||
@@ -440,7 +443,8 @@ func (h *FirewallHandler) CreateAddrGrp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -463,7 +467,8 @@ func (h *FirewallHandler) UpdateAddrGrp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -481,7 +486,8 @@ func (h *FirewallHandler) DeleteAddrGrp(c *gin.Context) {
|
||||
}
|
||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.addr_grp.delete",
|
||||
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 ───────────────────────────────────────────────────────────
|
||||
@@ -524,7 +530,8 @@ func (h *FirewallHandler) CreateService(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -547,7 +554,8 @@ func (h *FirewallHandler) UpdateService(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -565,7 +573,8 @@ func (h *FirewallHandler) DeleteService(c *gin.Context) {
|
||||
}
|
||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.service.delete",
|
||||
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 ─────────────────────────────────────────────────────
|
||||
@@ -608,7 +617,8 @@ func (h *FirewallHandler) CreateSvcGrp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -631,7 +641,8 @@ func (h *FirewallHandler) UpdateSvcGrp(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -649,7 +660,8 @@ func (h *FirewallHandler) DeleteSvcGrp(c *gin.Context) {
|
||||
}
|
||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.svc_grp.delete",
|
||||
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 ──────────────────────────────────────────────────────────────
|
||||
@@ -704,7 +716,8 @@ func (h *FirewallHandler) CreateRule(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -739,7 +752,8 @@ func (h *FirewallHandler) UpdateRule(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -757,7 +771,8 @@ func (h *FirewallHandler) DeleteRule(c *gin.Context) {
|
||||
}
|
||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.rule.delete",
|
||||
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) {
|
||||
@@ -850,7 +865,8 @@ func (h *FirewallHandler) CreateNAT(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -881,7 +897,8 @@ func (h *FirewallHandler) UpdateNAT(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
_ = 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) {
|
||||
@@ -899,7 +916,8 @@ func (h *FirewallHandler) DeleteNAT(c *gin.Context) {
|
||||
}
|
||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "fw.nat.delete",
|
||||
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) {
|
||||
|
||||
@@ -132,7 +132,7 @@ func (h *SystemHandler) Register(rg *gin.RouterGroup) {
|
||||
// Ergebnis für /cluster/system/load.
|
||||
//
|
||||
// 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) {
|
||||
g := rg.Group("/agent/system")
|
||||
g.GET("/health", h.Health)
|
||||
@@ -206,7 +206,7 @@ type serviceStatus struct {
|
||||
Label string `json:"label"`
|
||||
Unit string `json:"unit"`
|
||||
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
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ func (h *SystemHandler) Maintenance(c *gin.Context) {
|
||||
func (h *SystemHandler) ToggleMaintenance(c *gin.Context) {
|
||||
if h.Setup == nil {
|
||||
response.Err(c, http.StatusServiceUnavailable,
|
||||
simpleErr("setup not initialised"))
|
||||
simpleErr("setup not initialized"))
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
@@ -430,7 +430,7 @@ func (h *SystemHandler) BackupRetention(c *gin.Context) {
|
||||
// keep=0 → wieder Default, keep=1..365 → custom.
|
||||
func (h *SystemHandler) SetBackupRetention(c *gin.Context) {
|
||||
if h.Setup == nil {
|
||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialised"))
|
||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialized"))
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
@@ -466,7 +466,7 @@ func (h *SystemHandler) AuditRetention(c *gin.Context) {
|
||||
// SetAuditRetention setzt Audit-Retention in Tagen. 0..3650.
|
||||
func (h *SystemHandler) SetAuditRetention(c *gin.Context) {
|
||||
if h.Setup == nil {
|
||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialised"))
|
||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialized"))
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
@@ -513,7 +513,7 @@ func (h *SystemHandler) IPv6(c *gin.Context) {
|
||||
|
||||
func (h *SystemHandler) SetIPv6(c *gin.Context) {
|
||||
if h.Setup == nil {
|
||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialised"))
|
||||
response.Err(c, http.StatusServiceUnavailable, simpleErr("setup not initialized"))
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
@@ -582,7 +582,7 @@ LIMIT 10`)
|
||||
// HAProxyReload zwingt ein systemctl reload haproxy.service — nützlich
|
||||
// wenn der Operator manuell in /etc/edgeguard/tls/ geschrieben hat
|
||||
// (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) {
|
||||
out, err := exec.Command("sudo", "-n", "/usr/bin/systemctl", "reload", "haproxy.service").CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -600,12 +600,12 @@ func (h *SystemHandler) HAProxyReload(c *gin.Context) {
|
||||
// starten darf. edgeguard-api selbst ist bewusst ausgeschlossen (würde
|
||||
// die eigene HTTP-Response killen). postgresql ebenfalls (Datenpfad).
|
||||
var restartAllowlist = map[string]bool{
|
||||
"haproxy": true,
|
||||
"squid": true,
|
||||
"unbound": true,
|
||||
"chrony": true,
|
||||
"nftables": true,
|
||||
"wireguard": true, // wireguard als Metadienst; einzelne wg-Ifaces über wg-quick@<name>
|
||||
"haproxy": true,
|
||||
"squid": true,
|
||||
"unbound": true,
|
||||
"chrony": true,
|
||||
"nftables": true,
|
||||
"wireguard": true, // wireguard als Metadienst; einzelne wg-Ifaces über wg-quick@<name>
|
||||
"edgeguard-scheduler": true,
|
||||
}
|
||||
|
||||
@@ -920,7 +920,7 @@ func (h *SystemHandler) Upgrade(c *gin.Context) {
|
||||
// beiden Namespaces aus zugänglich.
|
||||
const scriptPath = "/var/lib/edgeguard/upgrade.sh"
|
||||
// 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
|
||||
// Index-Datei) → apt-resolver-fail mit "no choices are installable".
|
||||
// Drei Versuche mit 15s/30s Backoff geben Gitea Zeit den Index
|
||||
@@ -1235,4 +1235,3 @@ func flagsToList(f net.Flags) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package kea renders the Kea DHCPv4 server config from the dhcp_*
|
||||
// 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
|
||||
// at /etc/edgeguard/kea/kea-dhcp4.conf (edgeguard-owned); postinst
|
||||
// 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.
|
||||
func TestTemplateGARPRefresh(t *testing.T) {
|
||||
out := render(t, testView())
|
||||
|
||||
@@ -3,7 +3,7 @@ package models
|
||||
import "time"
|
||||
|
||||
// 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).
|
||||
// gw_check_ip = Gateway-IP für vrrp_script chk_gateway (0033).
|
||||
type ClusterSettings struct {
|
||||
|
||||
@@ -2,7 +2,7 @@ package models
|
||||
|
||||
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).
|
||||
type DHCPSettings struct {
|
||||
ID int `gorm:"column:id;primaryKey" json:"id"`
|
||||
|
||||
@@ -2,7 +2,7 @@ package models
|
||||
|
||||
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)
|
||||
// und wird NIE serialisiert (json:"-").
|
||||
type OIDCSettings struct {
|
||||
|
||||
@@ -2,7 +2,7 @@ package models
|
||||
|
||||
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).
|
||||
type RADIUSSettings struct {
|
||||
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"`
|
||||
|
||||
// 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
|
||||
// the model directly.
|
||||
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
|
||||
// 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
|
||||
// `make publish` ein verfügbares Update, statt 5 min auf die nächste
|
||||
// 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...)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func Summarise(results []Result) string {
|
||||
func Summarize(results []Result) string {
|
||||
var b strings.Builder
|
||||
for _, r := range results {
|
||||
if r.Skipped {
|
||||
|
||||
@@ -34,7 +34,7 @@ type Result struct {
|
||||
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,
|
||||
// Unterstrich. Whitespace, $, `, `;`, `&`, `|`, `>` etc. werden gesperrt.
|
||||
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
|
||||
// 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.
|
||||
type Authenticator interface {
|
||||
// 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
|
||||
// OIDC-Provider/Verifier. Login-Flow-State ist stateless (signiertes
|
||||
// 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"
|
||||
|
||||
// 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 {
|
||||
once sync.Once
|
||||
aead cipher.AEAD
|
||||
|
||||
@@ -15,12 +15,12 @@ import (
|
||||
"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.
|
||||
type ImportResult struct {
|
||||
IfacesAdded int `json:"ifaces_added"`
|
||||
PeersAdded int `json:"peers_added"`
|
||||
Skipped []string `json:"skipped,omitempty"` // ifaces already present, with reason
|
||||
IfacesAdded int `json:"ifaces_added"`
|
||||
PeersAdded int `json:"peers_added"`
|
||||
Skipped []string `json:"skipped,omitempty"` // ifaces already present, with reason
|
||||
}
|
||||
|
||||
// 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("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(fmt.Sprintf("SecRuleEngine %s\n", ruleEngineMode(cfg.Mode)))
|
||||
fmt.Fprintf(&sb, "SecRuleEngine %s\n", ruleEngineMode(cfg.Mode))
|
||||
|
||||
if crsDir != "" && crsAvailable(crsDir) {
|
||||
// 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 {
|
||||
pl = 1
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf(
|
||||
"SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=%d\"\n", pl,
|
||||
))
|
||||
fmt.Fprintf(&sb, "SecAction \"id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=%d\"\n", pl)
|
||||
setupConf := filepath.Join(crsDir, "crs-setup.conf")
|
||||
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")
|
||||
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).
|
||||
for _, id := range cfg.RuleExclusions {
|
||||
id = strings.TrimSpace(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
|
||||
// inspected request to the appropriate per-domain Coraza engine.
|
||||
type SPOEAgent struct {
|
||||
Manager *Manager
|
||||
AlertWriter *AlertWriter
|
||||
Addr string
|
||||
Manager *Manager
|
||||
AlertWriter *AlertWriter
|
||||
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 {
|
||||
agent := spop.Agent{
|
||||
Addr: a.Addr,
|
||||
|
||||
Reference in New Issue
Block a user