feat: Zonen als first-class Entity + Domain↔Backend-Verknüpfung sichtbar
* Migration 0012: firewall_zones (id, name UNIQUE, description, builtin),
Seed wan/lan/dmz/mgmt/cluster als builtin. CHECK-Constraints auf
network_interfaces.role + firewall_rules.{src,dst}_zone +
firewall_nat_rules.{in,out}_zone gedroppt — Validation lebt jetzt
app-side (Handler prüft Existenz in firewall_zones).
* Backend: firewall.ZonesRepo (CRUD + Exists + References-Lookup),
/api/v1/firewall/zones, builtin geschützt (Name nicht änderbar,
Delete blockiert), Rename eines Custom-Zone aktuell ohne Cascade
(Handler-Sorge bei Rules/NAT/Networks).
* Handler-Validation in CreateRule/UpdateRule/CreateNAT/UpdateNAT +
NetworksHandler: Zone-Existence-Check pro Mutation, 400 bei Tippfehler.
* Frontend: Firewall-Tab "Zonen" (CRUD mit builtin-Schutz). Networks-
Form lädt Rollen aus /firewall/zones (statt hardcoded Liste); Rules-
und NAT-Forms ziehen die Zone-Auswahl ebenfalls aus der API.
* Domain-Form bekommt Primary-Backend-Picker (Field war im Modell,
fehlte im UI). Backends-Tabelle zeigt umgekehrt welche Domains
darauf zeigen — bidirektionale Sicht ohne Schemaänderung.
* HAProxy-Renderer: safeID-FuncMap escaped Server-Namen mit Whitespace
("Control Master 1" → "Control_Master_1"). Vorher ist haproxy beim
Reload an Spaces im Backend-Namen kaputt gegangen.
* Version 1.0.3 → 1.0.6.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
internal/database/migrations/0012_firewall_zones.sql
Normal file
48
internal/database/migrations/0012_firewall_zones.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
-- firewall_zones promotes zones from a hard-coded enum
|
||||
-- (wan/lan/dmz/mgmt/cluster) into a first-class entity. Operators
|
||||
-- can add their own (e.g. iot, guest, voip) without a schema
|
||||
-- change. Existing role/zone TEXT columns on network_interfaces,
|
||||
-- firewall_rules and firewall_nat_rules continue to store the
|
||||
-- zone NAME — referential integrity is enforced at the application
|
||||
-- layer (handler validates name exists in firewall_zones), not by
|
||||
-- a hard FK, so 'any' on rules and NULL on NAT keep working
|
||||
-- without special-casing.
|
||||
--
|
||||
-- builtin = TRUE marks the seed zones; the API rejects DELETE on
|
||||
-- those rows to prevent the operator from removing a zone the
|
||||
-- renderer still expects.
|
||||
CREATE TABLE IF NOT EXISTS firewall_zones (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
builtin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT firewall_zones_name_check CHECK (name ~ '^[a-z][a-z0-9_-]{0,31}$')
|
||||
);
|
||||
|
||||
INSERT INTO firewall_zones (name, description, builtin) VALUES
|
||||
('wan', 'Public-facing internet uplink', TRUE),
|
||||
('lan', 'Internal trusted network', TRUE),
|
||||
('dmz', 'Quarantined service network', TRUE),
|
||||
('mgmt', 'Admin-only management network', TRUE),
|
||||
('cluster', 'Inter-node cluster traffic (KeyDB / mTLS API)', TRUE)
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
|
||||
-- Drop the hard-coded CHECK constraints so the operator can declare
|
||||
-- new zones without the SQL layer rejecting them. App-side
|
||||
-- validation in the handlers takes over.
|
||||
ALTER TABLE network_interfaces DROP CONSTRAINT IF EXISTS network_interfaces_role_check;
|
||||
ALTER TABLE firewall_rules DROP CONSTRAINT IF EXISTS firewall_rules_src_zone_check;
|
||||
ALTER TABLE firewall_rules DROP CONSTRAINT IF EXISTS firewall_rules_dst_zone_check;
|
||||
ALTER TABLE firewall_nat_rules DROP CONSTRAINT IF EXISTS firewall_nat_rules_zone_check;
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS firewall_zones;
|
||||
-- +goose StatementEnd
|
||||
Reference in New Issue
Block a user