feat(dhcp): DHCPv4-Server via Kea (kea-dhcp4-server) — v1.2.92
Verwalteter DHCPv4-Server analog Unbound/Squid/Chrony. - Migration 0041: dhcp_settings (singleton, node-lokal), dhcp_subnets, dhcp_reservations. - internal/kea: Renderer baut Kea-JSON via Go-Struct→Marshal (garantiert valide), managed /etc/edgeguard/kea/kea-dhcp4.conf (Symlink von /etc/kea), Service-Lifecycle an enabled gekoppelt (default AUS, kein rogue DHCP). Interface per NAME (cluster-sicher, kein node-lokaler FK). - internal/services/dhcp + internal/handlers/dhcp.go: Settings + Subnet/Reservation-CRUD, Validierung (CIDR/IP/MAC/interface exists). - configgen: Stop/Enable/DisableService. Firewall: AutoFWRule.Iface → udp/67 pro LAN-Interface gescopt (kein WAN). Cluster: subnets/reservations repliziert (hashSpec), dhcp_settings node-lokal (localOnlyTables). - main.go + render.go + WithAllReloaders Wiring. Packaging: kea-dhcp4-server Dependency, /etc/edgeguard/kea Dir, Symlink, disable-on-install, sudoers (restart/stop/enable/disable). - UI: DHCP-Seite (Settings + Subnets + Reservierungen pro Subnet), Route/Nav/i18n de/en, HA-Warnung 'nur auf einer Node aktivieren'. - Tests (guarded EG_FWTEST_DSN): Kea-Renderer gegen DB (valides JSON + Felder), FW-Auto-Rule-Iface inkl. nft -c. Scope v1: DHCPv4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
89
internal/kea/kea_test.go
Normal file
89
internal/kea/kea_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package kea
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
||||
)
|
||||
|
||||
// Guarded integration test: set EG_FWTEST_DSN (sonst skip).
|
||||
func TestRender_DHCPConfig(t *testing.T) {
|
||||
dsn := os.Getenv("EG_FWTEST_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("set EG_FWTEST_DSN to run the kea renderer test")
|
||||
}
|
||||
ctx := context.Background()
|
||||
var mErr error
|
||||
for i := 0; i < 3; i++ {
|
||||
if mErr = database.Migrate(ctx, dsn); mErr == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(700 * time.Millisecond)
|
||||
}
|
||||
if mErr != nil {
|
||||
t.Fatalf("migrate: %v", mErr)
|
||||
}
|
||||
pool, err := database.Open(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
for _, q := range []string{
|
||||
`DELETE FROM dhcp_reservations`,
|
||||
`DELETE FROM dhcp_subnets`,
|
||||
`UPDATE dhcp_settings SET enabled=true, default_lease=3600, max_lease=7200, domain_name='lan', dns_servers='1.1.1.1, 8.8.8.8' WHERE id=1`,
|
||||
} {
|
||||
if _, err := pool.Exec(ctx, q); err != nil {
|
||||
t.Fatalf("seed (%s): %v", q, err)
|
||||
}
|
||||
}
|
||||
var subID int64
|
||||
if err := pool.QueryRow(ctx, `
|
||||
INSERT INTO dhcp_subnets (name, interface_name, subnet_cidr, pool_start, pool_end, gateway, dns_servers, active)
|
||||
VALUES ('lan','eth1','10.0.0.0/24','10.0.0.100','10.0.0.200','10.0.0.1','',true) RETURNING id`).Scan(&subID); err != nil {
|
||||
t.Fatalf("seed subnet: %v", err)
|
||||
}
|
||||
if _, err := pool.Exec(ctx, `
|
||||
INSERT INTO dhcp_reservations (subnet_id, mac_address, ip_address, hostname, active)
|
||||
VALUES ($1,'aa:bb:cc:dd:ee:ff','10.0.0.50','printer',true)`, subID); err != nil {
|
||||
t.Fatalf("seed reservation: %v", err)
|
||||
}
|
||||
|
||||
out, err := New(pool).RenderToString(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("render: %v", err)
|
||||
}
|
||||
if !json.Valid([]byte(out)) {
|
||||
t.Fatalf("rendered config is not valid JSON:\n%s", out)
|
||||
}
|
||||
for _, want := range []string{
|
||||
`"10.0.0.0/24"`,
|
||||
`"10.0.0.100 - 10.0.0.200"`,
|
||||
`"aa:bb:cc:dd:ee:ff"`,
|
||||
`"routers"`,
|
||||
`"eth1"`,
|
||||
`"hw-address"`,
|
||||
`"valid-lifetime": 3600`,
|
||||
} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("rendered config missing %q\n----\n%s", want, out)
|
||||
}
|
||||
}
|
||||
|
||||
// Best-effort: echte Kea-Validierung, falls die Binary da ist.
|
||||
if _, statErr := os.Stat(keaBinary); statErr == nil {
|
||||
f, _ := os.CreateTemp(t.TempDir(), "kea-*.conf")
|
||||
_, _ = f.WriteString(out)
|
||||
f.Close()
|
||||
if combined, err := exec.Command(keaBinary, "-t", f.Name()).CombinedOutput(); err != nil {
|
||||
t.Fatalf("kea-dhcp4 -t rejected rendered config: %v\n%s", err, combined)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user